//! awk - Pattern scanning and processing builtin
//!
//! Implements basic AWK functionality.
//!
//! Usage:
//!   awk '{print $1}' file
//!   awk -F: '{print $1}' /etc/passwd
//!   echo "a b c" | awk '{print $2}'
//!   awk 'BEGIN{print "start"} {print} END{print "end"}' file
//!   awk '/pattern/{print}' file
//!   awk 'NR==2{print}' file

// Parser invariant: `pos` is always a byte offset on a UTF-8 char boundary.
// Move across user-controlled text with `advance()`/`consume_while()`, and use
// raw `pos += N` only for known ASCII tokens and delimiters.
#![allow(clippy::unwrap_used)]

use async_trait::async_trait;
use regex::Regex;
use std::collections::HashMap;

use super::search_common::build_regex;
use std::path::PathBuf;
use std::sync::Arc;

use super::{Builtin, Context, read_text_file};
use crate::error::{Error, Result};
use crate::fs::FileSystem;
use crate::interpreter::ExecResult;
use crate::limits::ExecutionLimits;

/// awk command - pattern scanning and processing
pub struct Awk;

#[derive(Debug)]
struct AwkProgram {
    begin_actions: Vec<AwkAction>,
    main_rules: Vec<AwkRule>,
    end_actions: Vec<AwkAction>,
    functions: HashMap<String, AwkFunctionDef>,
}

#[derive(Debug, Clone)]
struct AwkFunctionDef {
    params: Vec<String>,
    body: Vec<AwkAction>,
}

#[derive(Debug)]
struct AwkRule {
    pattern: Option<AwkPattern>,
    actions: Vec<AwkAction>,
}

#[derive(Debug)]
enum AwkPattern {
    Regex(Regex),
    Expression(AwkExpr),
    /// Range pattern: /start/,/end/ — matches from start to end inclusive.
    /// Each sub-pattern can be a Regex or Expression.
    Range(Box<AwkPattern>, Box<AwkPattern>),
}

#[derive(Debug, Clone)]
enum AwkExpr {
    Number(f64),
    String(String),
    Field(Box<AwkExpr>), // $n
    Variable(String),    // var
    BinOp(Box<AwkExpr>, String, Box<AwkExpr>),
    UnaryOp(String, Box<AwkExpr>),
    Assign(String, Box<AwkExpr>),
    ArrayAssign(String, Box<AwkExpr>, Box<AwkExpr>), // arr[key] = val
    CompoundArrayAssign(String, Box<AwkExpr>, String, Box<AwkExpr>), // arr[key] += val
    Concat(Vec<AwkExpr>),
    FuncCall(String, Vec<AwkExpr>),
    Regex(String),
    #[allow(dead_code)] // matched in eval but construction deferred to pattern expansion
    Match(Box<AwkExpr>, String), // expr ~ /pattern/
    PostIncrement(String),                   // var++
    PostDecrement(String),                   // var--
    PreIncrement(String),                    // ++var
    PreDecrement(String),                    // --var
    InArray(Box<AwkExpr>, String),           // key in arr
    FieldAssign(Box<AwkExpr>, Box<AwkExpr>), // $n = val
    /// getline [var] < file as expression — returns 1 on success, 0 on EOF, -1 on error
    GetlineFile {
        var: Option<String>,
        file: Box<AwkExpr>,
    },
}

/// Output target for print/printf redirection (e.g., `> file`, `>> file`).
/// Pipe (`| cmd`) is not supported and returns a clear error.
#[derive(Debug, Clone)]
enum AwkOutputTarget {
    /// Truncate/create file: `> file`
    Truncate(AwkExpr),
    /// Append to file: `>> file`
    Append(AwkExpr),
}

#[derive(Debug, Clone)]
enum AwkAction {
    Print(Vec<AwkExpr>, Option<AwkOutputTarget>),
    Printf(AwkExpr, Vec<AwkExpr>, Option<AwkOutputTarget>),
    Assign(String, AwkExpr),
    ArrayAssign(String, AwkExpr, AwkExpr), // arr[key] = val
    If(AwkExpr, Vec<AwkAction>, Vec<AwkAction>),
    While(AwkExpr, Vec<AwkAction>),
    DoWhile(AwkExpr, Vec<AwkAction>),
    For(Box<AwkAction>, AwkExpr, Box<AwkAction>, Vec<AwkAction>),
    ForIn(String, String, Vec<AwkAction>), // for (key in arr) { body }
    Next,
    Break,
    Continue,
    Delete(String, AwkExpr), // delete arr[key]
    Getline,                 // getline — read next input record into $0
    /// getline [var] < file — read next line from file
    GetlineFile {
        var: Option<String>,
        file: AwkExpr,
    },
    Exit(Option<AwkExpr>),
    Return(Option<AwkExpr>),
    Expression(AwkExpr),
}

struct AwkState {
    variables: HashMap<String, AwkValue>,
    fields: Vec<String>,
    fs: String,
    ofs: String,
    ors: String,
    nr: usize,
    nf: usize,
    fnr: usize,
    /// When true, fields are split per RFC 4180 CSV rules (--csv flag)
    csv_mode: bool,
}

#[derive(Debug, Clone, PartialEq)]
enum AwkValue {
    Number(f64),
    String(String),
    Uninitialized,
}

/// Format number using AWK's OFMT (%.6g): 6 significant digits, trim trailing zeros.
fn format_awk_number(n: f64) -> String {
    if n.is_nan() {
        return "nan".to_string();
    }
    if n.is_infinite() {
        return if n > 0.0 { "inf" } else { "-inf" }.to_string();
    }
    // Integers: no decimal point
    if n.fract() == 0.0 && n.abs() < 1e16 {
        return format!("{}", n as i64);
    }
    // %.6g: use 6 significant digits
    let abs = n.abs();
    let exp = abs.log10().floor() as i32;
    if !(-4..6).contains(&exp) {
        // Scientific notation: 5 decimal places = 6 sig digits
        let mut s = format!("{:.*e}", 5, n);
        // Trim trailing zeros in mantissa
        if let Some(e_pos) = s.find('e') {
            let (mantissa, exp_part) = s.split_at(e_pos);
            let trimmed = mantissa.trim_end_matches('0').trim_end_matches('.');
            s = format!("{}{}", trimmed, exp_part);
        }
        // Normalize exponent format: e1 -> e+01 etc. to match C printf
        // Actually AWK uses e+06 style. Rust uses e6. Fix:
        if let Some(e_pos) = s.find('e') {
            let exp_str = &s[e_pos + 1..];
            let exp_val: i32 = exp_str.parse().unwrap_or(0);
            let mantissa = &s[..e_pos];
            s = format!("{}e{:+03}", mantissa, exp_val);
        }
        s
    } else {
        // Fixed notation
        let decimal_places = (5 - exp).max(0) as usize;
        let mut s = format!("{:.*}", decimal_places, n);
        if s.contains('.') {
            s = s.trim_end_matches('0').trim_end_matches('.').to_string();
        }
        s
    }
}

impl AwkValue {
    fn as_number(&self) -> f64 {
        match self {
            AwkValue::Number(n) => *n,
            AwkValue::String(s) => s.parse().unwrap_or(0.0),
            AwkValue::Uninitialized => 0.0,
        }
    }

    fn as_string(&self) -> String {
        match self {
            AwkValue::Number(n) => format_awk_number(*n),
            AwkValue::String(s) => s.clone(),
            AwkValue::Uninitialized => String::new(),
        }
    }

    fn as_bool(&self) -> bool {
        match self {
            AwkValue::Number(n) => *n != 0.0,
            AwkValue::String(s) => {
                if s.is_empty() {
                    return false;
                }
                // In awk, numeric strings evaluate as numbers in boolean context
                if let Ok(n) = s.parse::<f64>() {
                    n != 0.0
                } else {
                    true
                }
            }
            AwkValue::Uninitialized => false,
        }
    }
}

impl Default for AwkState {
    fn default() -> Self {
        let mut variables = HashMap::new();
        // POSIX SUBSEP: subscript separator for multi-dimensional arrays
        variables.insert("SUBSEP".to_string(), AwkValue::String("\x1c".to_string()));
        Self {
            variables,
            fields: Vec::new(),
            fs: " ".to_string(),
            ofs: " ".to_string(),
            ors: "\n".to_string(),
            nr: 0,
            nf: 0,
            fnr: 0,
            csv_mode: false,
        }
    }
}

/// Parse a CSV line per RFC 4180: handle quoted fields, embedded commas,
/// and double-quote escaping.
fn csv_split_fields(line: &str) -> Vec<String> {
    let mut fields = Vec::new();
    let mut field = String::new();
    let mut in_quotes = false;
    let mut chars = line.chars().peekable();

    while let Some(c) = chars.next() {
        if in_quotes {
            if c == '"' {
                if chars.peek() == Some(&'"') {
                    // Escaped quote
                    field.push('"');
                    chars.next();
                } else {
                    in_quotes = false;
                }
            } else {
                field.push(c);
            }
        } else if c == '"' {
            in_quotes = true;
        } else if c == ',' {
            fields.push(std::mem::take(&mut field));
        } else {
            field.push(c);
        }
    }
    fields.push(field);
    fields
}

impl AwkState {
    /// Split a line into fields based on current mode (CSV or FS)
    fn split_fields(&self, line: &str) -> Vec<String> {
        if self.csv_mode {
            csv_split_fields(line)
        } else if self.fs == " " {
            line.split_whitespace().map(String::from).collect()
        } else {
            line.split(&self.fs).map(String::from).collect()
        }
    }

    fn set_line(&mut self, line: &str) {
        self.nr += 1;
        self.fnr += 1;

        // Split by field separator
        self.fields = self.split_fields(line);

        self.nf = self.fields.len();

        // Set built-in variables
        self.variables
            .insert("NR".to_string(), AwkValue::Number(self.nr as f64));
        self.variables
            .insert("NF".to_string(), AwkValue::Number(self.nf as f64));
        self.variables
            .insert("FNR".to_string(), AwkValue::Number(self.fnr as f64));
        self.variables
            .insert("$0".to_string(), AwkValue::String(line.to_string()));
    }

    fn get_field(&self, n: usize) -> AwkValue {
        if n == 0 {
            // $0 is the whole line
            self.variables
                .get("$0")
                .cloned()
                .unwrap_or(AwkValue::Uninitialized)
        } else if n <= self.fields.len() {
            AwkValue::String(self.fields[n - 1].clone())
        } else {
            AwkValue::Uninitialized
        }
    }

    fn get_variable(&self, name: &str) -> AwkValue {
        match name {
            "NR" => AwkValue::Number(self.nr as f64),
            "NF" => AwkValue::Number(self.nf as f64),
            "FNR" => AwkValue::Number(self.fnr as f64),
            "FS" => AwkValue::String(self.fs.clone()),
            "OFS" => AwkValue::String(self.ofs.clone()),
            "ORS" => AwkValue::String(self.ors.clone()),
            _ => self
                .variables
                .get(name)
                .cloned()
                .unwrap_or(AwkValue::Uninitialized),
        }
    }

    fn set_variable(&mut self, name: &str, value: AwkValue) {
        match name {
            "FS" => self.fs = value.as_string(),
            "OFS" => self.ofs = value.as_string(),
            "ORS" => self.ors = value.as_string(),
            "$0" => {
                let s = value.as_string();
                // Re-split fields when $0 is modified
                self.fields = self.split_fields(&s);
                self.nf = self.fields.len();
                self.variables
                    .insert("NF".to_string(), AwkValue::Number(self.nf as f64));
                self.variables.insert(name.to_string(), value);
            }
            _ => {
                self.variables.insert(name.to_string(), value);
            }
        }
    }
}

/// THREAT[TM-DOS-027]: Maximum recursion depth for awk expression parser.
/// Prevents stack overflow from deeply nested expressions like `(((((...)))))`
/// or deeply chained unary operators like `- - - - - x`.
/// Set conservatively: each recursion level uses ~1-2KB stack in debug mode.
/// 100 levels × ~2KB = ~200KB, well within typical stack limits.
const MAX_AWK_PARSER_DEPTH: usize = 100;

/// Preprocess awk program: replace newlines with semicolons inside action blocks.
/// This makes newlines act as statement separators per POSIX awk spec.
/// Respects string literals, regex literals, and nested braces.
fn normalize_awk_newlines(input: &str) -> String {
    let mut result = String::with_capacity(input.len());
    let chars: Vec<char> = input.chars().collect();
    let mut i = 0;
    let mut brace_depth = 0;

    while i < chars.len() {
        match chars[i] {
            '{' => {
                brace_depth += 1;
                result.push('{');
                i += 1;
            }
            '}' => {
                if brace_depth > 0 {
                    brace_depth -= 1;
                }
                result.push('}');
                i += 1;
            }
            '"' => {
                // String literal — pass through unchanged
                result.push('"');
                i += 1;
                while i < chars.len() && chars[i] != '"' {
                    if chars[i] == '\\' && i + 1 < chars.len() {
                        result.push(chars[i]);
                        i += 1;
                    }
                    result.push(chars[i]);
                    i += 1;
                }
                if i < chars.len() {
                    result.push(chars[i]); // closing "
                    i += 1;
                }
            }
            '/' => {
                // Regex literal — pass through unchanged (both pattern and expression context)
                result.push('/');
                i += 1;
                while i < chars.len() && chars[i] != '/' {
                    if chars[i] == '\\' && i + 1 < chars.len() {
                        result.push(chars[i]);
                        i += 1;
                    }
                    result.push(chars[i]);
                    i += 1;
                }
                if i < chars.len() {
                    result.push(chars[i]); // closing /
                    i += 1;
                }
            }
            '#' => {
                // Comment — skip to end of line, replace with newline/semicolon
                while i < chars.len() && chars[i] != '\n' {
                    i += 1;
                }
                if i < chars.len() {
                    if brace_depth > 0 {
                        result.push(';');
                    } else {
                        result.push('\n');
                    }
                    i += 1;
                }
            }
            '\\' if i + 1 < chars.len() && chars[i + 1] == '\n' => {
                // Backslash-newline: line continuation — join lines
                i += 2;
            }
            '\n' if brace_depth > 0 => {
                // Inside action block: replace newline with semicolon
                result.push(';');
                i += 1;
            }
            _ => {
                result.push(chars[i]);
                i += 1;
            }
        }
    }
    result
}

struct AwkParser<'a> {
    input: &'a str,
    pos: usize,
    /// Current recursion depth for expression parsing
    depth: usize,
    /// When true, `>` and `>>` are output redirection, not comparison ops.
    /// Set during print/printf argument parsing per POSIX awk semantics.
    in_print_context: bool,
}

impl<'a> AwkParser<'a> {
    fn is_identifier_start(c: char) -> bool {
        c.is_alphabetic() || c == '_'
    }

    fn is_identifier_continue(c: char) -> bool {
        c.is_alphanumeric() || c == '_'
    }

    fn new(input: &'a str) -> Self {
        Self {
            input,
            pos: 0,
            depth: 0,
            in_print_context: false,
        }
    }

    /// Get the character at the current byte position (char-boundary safe).
    /// Returns None if pos is at or past end of input.
    fn current_char(&self) -> Option<char> {
        self.input[self.pos..].chars().next()
    }

    /// Advance pos past the current character (handles multi-byte UTF-8).
    fn advance(&mut self) {
        if let Some(c) = self.current_char() {
            self.pos += c.len_utf8();
        }
    }

    fn consume_while(&mut self, predicate: fn(char) -> bool) {
        while self.pos < self.input.len() {
            let c = self.current_char().unwrap();
            if predicate(c) {
                self.advance();
            } else {
                break;
            }
        }
    }

    /// THREAT[TM-DOS-027]: Increment depth, error if limit exceeded
    fn push_depth(&mut self) -> Result<()> {
        self.depth += 1;
        if self.depth > MAX_AWK_PARSER_DEPTH {
            return Err(Error::Execution(format!(
                "awk: expression nesting too deep ({} levels, max {})",
                self.depth, MAX_AWK_PARSER_DEPTH
            )));
        }
        Ok(())
    }

    /// Decrement depth after leaving a recursive parse
    fn pop_depth(&mut self) {
        if self.depth > 0 {
            self.depth -= 1;
        }
    }

    fn parse(&mut self) -> Result<AwkProgram> {
        let mut program = AwkProgram {
            begin_actions: Vec::new(),
            main_rules: Vec::new(),
            end_actions: Vec::new(),
            functions: HashMap::new(),
        };

        self.skip_whitespace();

        while self.pos < self.input.len() {
            self.skip_whitespace();
            if self.pos >= self.input.len() {
                break;
            }

            // Check for function/BEGIN/END
            if self.matches_keyword("function") {
                self.skip_whitespace();
                let (name, func_def) = self.parse_function_def()?;
                program.functions.insert(name, func_def);
            } else if self.matches_keyword("BEGIN") {
                self.skip_whitespace();
                let actions = self.parse_action_block()?;
                program.begin_actions.extend(actions);
            } else if self.matches_keyword("END") {
                self.skip_whitespace();
                let actions = self.parse_action_block()?;
                program.end_actions.extend(actions);
            } else {
                // Pattern-action rule
                let rule = self.parse_rule()?;
                program.main_rules.push(rule);
            }

            self.skip_whitespace();
        }

        // If no rules, add default print rule
        if program.main_rules.is_empty()
            && program.begin_actions.is_empty()
            && program.end_actions.is_empty()
        {
            program.main_rules.push(AwkRule {
                pattern: None,
                actions: vec![AwkAction::Print(
                    vec![AwkExpr::Field(Box::new(AwkExpr::Number(0.0)))],
                    None,
                )],
            });
        }

        Ok(program)
    }

    /// Parse a user-defined function: function name(params) { body }
    fn parse_function_def(&mut self) -> Result<(String, AwkFunctionDef)> {
        // Parse function name
        let name = self.read_identifier()?;
        self.skip_whitespace();

        // Expect '('
        if self.pos >= self.input.len() || self.current_char().unwrap() != '(' {
            return Err(Error::Execution(
                "awk: expected '(' after function name".to_string(),
            ));
        }
        self.pos += 1;

        // Parse parameter list
        let mut params = Vec::new();
        self.skip_whitespace();
        while self.pos < self.input.len() && self.current_char().unwrap() != ')' {
            if !params.is_empty() {
                if self.current_char().unwrap() == ',' {
                    self.pos += 1;
                }
                self.skip_whitespace();
            }
            if self.pos < self.input.len() && self.current_char().unwrap() != ')' {
                params.push(self.read_identifier()?);
                self.skip_whitespace();
            }
        }

        // Expect ')'
        if self.pos >= self.input.len() || self.current_char().unwrap() != ')' {
            return Err(Error::Execution(
                "awk: expected ')' after function parameters".to_string(),
            ));
        }
        self.pos += 1;
        self.skip_whitespace();

        // Parse function body as action block
        let body = self.parse_action_block()?;

        Ok((name, AwkFunctionDef { params, body }))
    }

    /// Read an identifier (alphanumeric + underscore)
    fn read_identifier(&mut self) -> Result<String> {
        let start = self.pos;
        self.consume_while(Self::is_identifier_continue);
        if self.pos == start {
            return Err(Error::Execution("awk: expected identifier".to_string()));
        }
        Ok(self.input[start..self.pos].to_string())
    }

    fn matches_keyword(&mut self, keyword: &str) -> bool {
        if self.input[self.pos..].starts_with(keyword) {
            let after = self.pos + keyword.len();
            if after >= self.input.len() || {
                let c = self.input[after..].chars().next().unwrap();
                !c.is_alphanumeric() && c != '_'
            } {
                self.pos = after;
                return true;
            }
        }
        false
    }

    fn skip_whitespace(&mut self) {
        while self.pos < self.input.len() {
            let c = self.current_char().unwrap();
            if c.is_whitespace() {
                self.advance();
            } else if c == '#' {
                // Comment - skip to end of line (may contain multi-byte chars)
                while self.pos < self.input.len() && self.current_char().unwrap() != '\n' {
                    self.advance();
                }
            } else {
                break;
            }
        }
    }

    fn parse_rule(&mut self) -> Result<AwkRule> {
        let pattern = self.parse_pattern()?;
        self.skip_whitespace();

        let actions = if self.pos < self.input.len() && self.current_char().unwrap() == '{' {
            self.parse_action_block()?
        } else if pattern.is_some() {
            // Default action is print
            vec![AwkAction::Print(
                vec![AwkExpr::Field(Box::new(AwkExpr::Number(0.0)))],
                None,
            )]
        } else {
            Vec::new()
        };

        Ok(AwkRule { pattern, actions })
    }

    /// Parse a `/regex/` literal into an `AwkPattern::Regex`.
    /// Assumes the leading `/` is at `self.pos`.
    fn parse_regex_pattern(&mut self) -> Result<AwkPattern> {
        self.pos += 1; // skip opening '/'
        let start = self.pos;
        while self.pos < self.input.len() {
            let c = self.current_char().unwrap();
            if c == '/' {
                let pattern = &self.input[start..self.pos];
                self.pos += 1;
                let regex = build_regex(pattern)
                    .map_err(|e| Error::Execution(format!("awk: invalid regex: {}", e)))?;
                return Ok(AwkPattern::Regex(regex));
            } else if c == '\\' {
                self.pos += 1; // skip '\\' (ASCII)
                self.advance(); // skip next char (may be multi-byte)
            } else {
                self.advance(); // regex content may be multi-byte
            }
        }
        Err(Error::Execution("awk: unterminated regex".to_string()))
    }

    fn parse_pattern(&mut self) -> Result<Option<AwkPattern>> {
        self.skip_whitespace();

        if self.pos >= self.input.len() {
            return Ok(None);
        }

        let c = self.current_char().unwrap();

        // Check for regex pattern
        let first: Option<AwkPattern> = if c == '/' {
            Some(self.parse_regex_pattern()?)
        } else if c == '{' {
            // Check for opening brace (no pattern)
            return Ok(None);
        } else {
            // Expression pattern
            let expr = self.parse_expression()?;
            Some(AwkPattern::Expression(expr))
        };

        // Check for range pattern: pattern1 , pattern2
        if let Some(first_pat) = first {
            self.skip_whitespace();
            if self.pos < self.input.len() && self.current_char().unwrap() == ',' {
                self.pos += 1; // consume ','
                let second = self.parse_pattern()?;
                let second_pat = second.ok_or_else(|| {
                    Error::Execution("awk: expected second pattern in range".to_string())
                })?;
                Ok(Some(AwkPattern::Range(
                    Box::new(first_pat),
                    Box::new(second_pat),
                )))
            } else {
                Ok(Some(first_pat))
            }
        } else {
            Ok(None)
        }
    }

    fn parse_action_block(&mut self) -> Result<Vec<AwkAction>> {
        self.skip_whitespace();

        if self.pos >= self.input.len() || self.current_char().unwrap() != '{' {
            return Err(Error::Execution("awk: expected '{'".to_string()));
        }
        self.pos += 1;

        let mut actions = Vec::new();

        loop {
            self.skip_whitespace();
            if self.pos >= self.input.len() {
                return Err(Error::Execution(
                    "awk: unterminated action block".to_string(),
                ));
            }

            // Skip empty statements (consecutive semicolons from newline normalization)
            while self.pos < self.input.len() && self.current_char().unwrap() == ';' {
                self.pos += 1;
                self.skip_whitespace();
            }
            if self.pos >= self.input.len() {
                return Err(Error::Execution(
                    "awk: unterminated action block".to_string(),
                ));
            }

            let c = self.current_char().unwrap();
            if c == '}' {
                self.pos += 1;
                break;
            }

            let action = self.parse_action()?;
            actions.push(action);

            self.skip_whitespace();
            // Allow semicolon separator
            if self.pos < self.input.len() && self.current_char().unwrap() == ';' {
                self.pos += 1;
            }
        }

        Ok(actions)
    }

    fn parse_action(&mut self) -> Result<AwkAction> {
        self.skip_whitespace();

        // Check for keywords
        if self.matches_keyword("print") {
            return self.parse_print();
        }
        if self.matches_keyword("printf") {
            return self.parse_printf();
        }
        if self.matches_keyword("next") {
            return Ok(AwkAction::Next);
        }
        if self.matches_keyword("break") {
            return Ok(AwkAction::Break);
        }
        if self.matches_keyword("continue") {
            return Ok(AwkAction::Continue);
        }
        if self.matches_keyword("delete") {
            return self.parse_delete();
        }
        if self.matches_keyword("getline") {
            return self.parse_getline();
        }
        if self.matches_keyword("exit") {
            self.skip_whitespace();
            if self.pos < self.input.len() {
                let c = self.current_char().unwrap();
                if c != '}' && c != ';' {
                    let expr = self.parse_expression()?;
                    return Ok(AwkAction::Exit(Some(expr)));
                }
            }
            return Ok(AwkAction::Exit(None));
        }
        if self.matches_keyword("return") {
            self.skip_whitespace();
            if self.pos < self.input.len() {
                let c = self.current_char().unwrap();
                if c != '}' && c != ';' {
                    let expr = self.parse_expression()?;
                    return Ok(AwkAction::Return(Some(expr)));
                }
            }
            return Ok(AwkAction::Return(None));
        }
        if self.matches_keyword("if") {
            return self.parse_if();
        }
        if self.matches_keyword("for") {
            return self.parse_for();
        }
        if self.matches_keyword("while") {
            return self.parse_while();
        }
        if self.matches_keyword("do") {
            return self.parse_do_while();
        }

        // Otherwise it's an expression (including assignment)
        let expr = self.parse_expression()?;

        // Check if it's an assignment
        match expr {
            AwkExpr::Assign(name, val) => Ok(AwkAction::Assign(name, *val)),
            AwkExpr::ArrayAssign(name, key, val) => Ok(AwkAction::ArrayAssign(name, *key, *val)),
            _ => Ok(AwkAction::Expression(expr)),
        }
    }

    fn parse_print(&mut self) -> Result<AwkAction> {
        self.skip_whitespace();
        let mut args = Vec::new();

        // Enable print context so `>` and `>>` are not parsed as comparison
        self.in_print_context = true;

        loop {
            if self.pos >= self.input.len() {
                break;
            }
            let c = self.current_char().unwrap();
            if c == '}' || c == ';' {
                break;
            }
            // Stop at output redirection operators
            if c == '>' || c == '|' {
                break;
            }

            let expr = self.parse_expression()?;
            args.push(expr);

            self.skip_whitespace();
            if self.pos < self.input.len() && self.current_char().unwrap() == ',' {
                self.pos += 1;
                self.skip_whitespace();
            } else {
                break;
            }
        }

        if args.is_empty() {
            args.push(AwkExpr::Field(Box::new(AwkExpr::Number(0.0))));
        }

        let target = self.parse_output_target()?;
        self.in_print_context = false;

        Ok(AwkAction::Print(args, target))
    }

    fn parse_printf(&mut self) -> Result<AwkAction> {
        self.skip_whitespace();

        // Enable print context so `>` and `>>` are not parsed as comparison
        self.in_print_context = true;

        // Handle optional parenthesized form: printf("format", args)
        let has_parens = self.pos < self.input.len() && self.current_char().unwrap() == '(';
        if has_parens {
            self.pos += 1;
            self.skip_whitespace();
        }

        // Parse format string — accepts string literals or expressions
        if self.pos >= self.input.len() {
            self.in_print_context = false;
            return Err(Error::Execution(
                "awk: printf requires format string".to_string(),
            ));
        }

        let format_expr = if self.current_char().unwrap() == '"' {
            // String literal format — parse directly
            let s = self.parse_string()?;
            AwkExpr::String(s)
        } else {
            // Expression as format string (e.g., printf substr($1,1,1))
            self.parse_expression()?
        };
        let mut args = Vec::new();

        self.skip_whitespace();
        while self.pos < self.input.len() && self.current_char().unwrap() == ',' {
            self.pos += 1;
            self.skip_whitespace();
            let expr = self.parse_expression()?;
            args.push(expr);
            self.skip_whitespace();
        }

        if has_parens && self.pos < self.input.len() && self.current_char().unwrap() == ')' {
            self.pos += 1;
        }

        let target = self.parse_output_target()?;
        self.in_print_context = false;

        Ok(AwkAction::Printf(format_expr, args, target))
    }

    /// Parse optional output target after print/printf arguments: `> file`, `>> file`, `| cmd`.
    /// Pipe is unsupported and returns a clear error.
    fn parse_output_target(&mut self) -> Result<Option<AwkOutputTarget>> {
        self.skip_whitespace();
        if self.pos >= self.input.len() {
            return Ok(None);
        }

        let c = self.current_char().unwrap();
        if c == '>' {
            self.pos += 1;
            // Check for >>
            let append = self.pos < self.input.len() && self.current_char().unwrap() == '>';
            if append {
                self.pos += 1;
            }
            self.skip_whitespace();
            // Temporarily disable print context to parse the target as a normal expression
            self.in_print_context = false;
            let target = self.parse_expression()?;
            self.in_print_context = true;
            if append {
                Ok(Some(AwkOutputTarget::Append(target)))
            } else {
                Ok(Some(AwkOutputTarget::Truncate(target)))
            }
        } else if c == '|' {
            // Pipe output (e.g., `print ... | "cmd"`) not supported in virtual mode
            Err(Error::Execution(
                "awk: pipe output redirection (|) is not supported".to_string(),
            ))
        } else {
            Ok(None)
        }
    }

    /// THREAT[TM-DOS-027]: Track depth for nested if/action blocks
    fn parse_if(&mut self) -> Result<AwkAction> {
        self.push_depth()?;

        self.skip_whitespace();

        if self.pos >= self.input.len() || self.current_char().unwrap() != '(' {
            self.pop_depth();
            return Err(Error::Execution("awk: expected '(' after if".to_string()));
        }
        self.pos += 1;

        let condition = self.parse_expression()?;

        self.skip_whitespace();
        if self.pos >= self.input.len() || self.current_char().unwrap() != ')' {
            self.pop_depth();
            return Err(Error::Execution(
                "awk: expected ')' after condition".to_string(),
            ));
        }
        self.pos += 1;

        self.skip_whitespace();
        let then_actions = if self.current_char().unwrap() == '{' {
            self.parse_action_block()?
        } else {
            vec![self.parse_action()?]
        };

        self.skip_whitespace();
        // Consume optional ';' before else
        if self.pos < self.input.len() && self.current_char().unwrap() == ';' {
            self.pos += 1;
            self.skip_whitespace();
        }
        let else_actions = if self.matches_keyword("else") {
            self.skip_whitespace();
            if self.pos < self.input.len() && self.current_char().unwrap() == '{' {
                self.parse_action_block()?
            } else {
                vec![self.parse_action()?]
            }
        } else {
            Vec::new()
        };

        self.pop_depth();
        Ok(AwkAction::If(condition, then_actions, else_actions))
    }

    fn parse_for(&mut self) -> Result<AwkAction> {
        self.skip_whitespace();

        if self.pos >= self.input.len() || self.current_char().unwrap() != '(' {
            return Err(Error::Execution("awk: expected '(' after for".to_string()));
        }
        self.pos += 1;
        self.skip_whitespace();

        // Check for `for (key in arr)` syntax
        let saved_pos = self.pos;
        if let Ok(AwkExpr::Variable(var_name)) = self.parse_primary() {
            self.skip_whitespace();
            if self.matches_keyword("in") {
                self.skip_whitespace();
                // Parse array name
                let start = self.pos;
                self.consume_while(Self::is_identifier_continue);
                let arr_name = self.input[start..self.pos].to_string();

                self.skip_whitespace();
                if self.pos >= self.input.len() || self.current_char().unwrap() != ')' {
                    return Err(Error::Execution("awk: expected ')' in for-in".to_string()));
                }
                self.pos += 1;

                self.skip_whitespace();
                let body = if self.pos < self.input.len() && self.current_char().unwrap() == '{' {
                    self.parse_action_block()?
                } else {
                    vec![self.parse_action()?]
                };

                return Ok(AwkAction::ForIn(var_name, arr_name, body));
            }
        }

        // Not for-in, backtrack and parse C-style for
        self.pos = saved_pos;

        // Parse init
        let init_expr = self.parse_expression()?;
        let init = match init_expr {
            AwkExpr::Assign(name, val) => AwkAction::Assign(name, *val),
            AwkExpr::ArrayAssign(name, key, val) => AwkAction::ArrayAssign(name, *key, *val),
            _ => AwkAction::Expression(init_expr),
        };

        self.skip_whitespace();
        if self.pos >= self.input.len() || self.current_char().unwrap() != ';' {
            return Err(Error::Execution(
                "awk: expected ';' in for statement".to_string(),
            ));
        }
        self.pos += 1;

        // Parse condition
        self.skip_whitespace();
        let condition = self.parse_expression()?;

        self.skip_whitespace();
        if self.pos >= self.input.len() || self.current_char().unwrap() != ';' {
            return Err(Error::Execution(
                "awk: expected ';' in for statement".to_string(),
            ));
        }
        self.pos += 1;

        // Parse update
        self.skip_whitespace();
        let update_expr = self.parse_expression()?;
        let update = match update_expr {
            AwkExpr::Assign(name, val) => AwkAction::Assign(name, *val),
            AwkExpr::ArrayAssign(name, key, val) => AwkAction::ArrayAssign(name, *key, *val),
            _ => AwkAction::Expression(update_expr),
        };

        self.skip_whitespace();
        if self.pos >= self.input.len() || self.current_char().unwrap() != ')' {
            return Err(Error::Execution(
                "awk: expected ')' in for statement".to_string(),
            ));
        }
        self.pos += 1;

        self.skip_whitespace();
        let body = if self.pos < self.input.len() && self.current_char().unwrap() == '{' {
            self.parse_action_block()?
        } else {
            vec![self.parse_action()?]
        };

        Ok(AwkAction::For(
            Box::new(init),
            condition,
            Box::new(update),
            body,
        ))
    }

    fn parse_while(&mut self) -> Result<AwkAction> {
        self.skip_whitespace();

        if self.pos >= self.input.len() || self.current_char().unwrap() != '(' {
            return Err(Error::Execution(
                "awk: expected '(' after while".to_string(),
            ));
        }
        self.pos += 1;

        let condition = self.parse_expression()?;

        self.skip_whitespace();
        if self.pos >= self.input.len() || self.current_char().unwrap() != ')' {
            return Err(Error::Execution(
                "awk: expected ')' after while condition".to_string(),
            ));
        }
        self.pos += 1;

        self.skip_whitespace();
        let body = if self.pos < self.input.len() && self.current_char().unwrap() == '{' {
            self.parse_action_block()?
        } else {
            vec![self.parse_action()?]
        };

        Ok(AwkAction::While(condition, body))
    }

    fn parse_do_while(&mut self) -> Result<AwkAction> {
        self.skip_whitespace();

        let body = if self.pos < self.input.len() && self.current_char().unwrap() == '{' {
            self.parse_action_block()?
        } else {
            vec![self.parse_action()?]
        };

        self.skip_whitespace();
        if !self.matches_keyword("while") {
            return Err(Error::Execution(
                "awk: expected 'while' after do body".to_string(),
            ));
        }

        self.skip_whitespace();
        if self.pos >= self.input.len() || self.current_char().unwrap() != '(' {
            return Err(Error::Execution(
                "awk: expected '(' after do-while".to_string(),
            ));
        }
        self.pos += 1;

        let condition = self.parse_expression()?;

        self.skip_whitespace();
        if self.pos >= self.input.len() || self.current_char().unwrap() != ')' {
            return Err(Error::Execution(
                "awk: expected ')' in do-while".to_string(),
            ));
        }
        self.pos += 1;

        Ok(AwkAction::DoWhile(condition, body))
    }

    fn parse_delete(&mut self) -> Result<AwkAction> {
        self.skip_whitespace();

        // Parse array name
        let start = self.pos;
        self.consume_while(Self::is_identifier_continue);
        let arr_name = self.input[start..self.pos].to_string();

        self.skip_whitespace();
        if self.pos < self.input.len() && self.current_char().unwrap() == '[' {
            self.pos += 1;
            let index = self.parse_expression()?;
            self.skip_whitespace();
            if self.pos >= self.input.len() || self.current_char().unwrap() != ']' {
                return Err(Error::Execution("awk: expected ']'".to_string()));
            }
            self.pos += 1;
            Ok(AwkAction::Delete(arr_name, index))
        } else {
            // delete entire array
            Ok(AwkAction::Delete(
                arr_name,
                AwkExpr::String("*".to_string()),
            ))
        }
    }

    /// Parse `getline [var] [< file]`.
    ///
    /// Forms:
    /// - `getline`           — read next input record into $0
    /// - `getline var`       — read next input record into var
    /// - `getline var < file` — read next line from file into var
    /// - `getline < file`    — read next line from file into $0
    fn parse_getline(&mut self) -> Result<AwkAction> {
        self.skip_whitespace();

        // Check what follows: variable name, '<', or end of statement
        let mut var: Option<String> = None;

        if self.pos < self.input.len() {
            let c = self.current_char().unwrap();
            // If next char is an identifier start (not '<', ';', '}', etc.), parse variable
            if Self::is_identifier_start(c) {
                let start = self.pos;
                self.advance();
                self.consume_while(Self::is_identifier_continue);
                var = Some(self.input[start..self.pos].to_string());
                self.skip_whitespace();
            }
        }

        // Check for '< file' redirection
        if self.pos < self.input.len() && self.current_char().unwrap() == '<' {
            self.pos += 1; // consume '<'
            self.skip_whitespace();
            let file = self.parse_primary()?;
            return Ok(AwkAction::GetlineFile { var, file });
        }

        // Plain getline (with optional var — but plain `getline var` without
        // file redirection just reads next input line into var; for now we
        // treat that the same as plain getline which updates $0).
        // TODO: `getline var` should store into var without updating $0
        Ok(AwkAction::Getline)
    }

    /// Parse `getline [var] [< file]` as an expression returning 1/0/-1.
    fn parse_getline_expr(&mut self) -> Result<AwkExpr> {
        self.skip_whitespace();

        let mut var: Option<String> = None;

        if self.pos < self.input.len() {
            let c = self.current_char().unwrap();
            if Self::is_identifier_start(c) {
                let start = self.pos;
                self.advance();
                self.consume_while(Self::is_identifier_continue);
                var = Some(self.input[start..self.pos].to_string());
                self.skip_whitespace();
            }
        }

        // Check for '< file' redirection
        if self.pos < self.input.len() && self.current_char().unwrap() == '<' {
            self.pos += 1; // consume '<'
            self.skip_whitespace();
            let file = self.parse_primary()?;
            return Ok(AwkExpr::GetlineFile {
                var,
                file: Box::new(file),
            });
        }

        // Plain getline expression without file — use FuncCall to represent
        // TODO: support plain getline as expression (advance input line)
        Ok(AwkExpr::FuncCall("__getline".to_string(), vec![]))
    }

    /// THREAT[TM-DOS-027]: Track depth on every expression entry
    fn parse_expression(&mut self) -> Result<AwkExpr> {
        self.push_depth()?;
        let result = self.parse_assignment();
        self.pop_depth();
        result
    }

    fn parse_assignment(&mut self) -> Result<AwkExpr> {
        let expr = self.parse_ternary()?;

        self.skip_whitespace();
        if self.pos >= self.input.len() {
            return Ok(expr);
        }

        // Check for compound assignment operators (+=, -=, *=, /=, %=)
        let compound_ops = ["+=", "-=", "*=", "/=", "%="];
        for op in compound_ops {
            if self.input[self.pos..].starts_with(op) {
                self.pos += op.len();
                self.skip_whitespace();
                let value = self.parse_assignment()?;

                match &expr {
                    AwkExpr::Variable(name) => {
                        let bin_op = &op[..1];
                        let current = AwkExpr::Variable(name.clone());
                        let combined =
                            AwkExpr::BinOp(Box::new(current), bin_op.to_string(), Box::new(value));
                        return Ok(AwkExpr::Assign(name.clone(), Box::new(combined)));
                    }
                    AwkExpr::FuncCall(fname, args)
                        if fname == "__array_access" && args.len() == 2 =>
                    {
                        if let AwkExpr::Variable(arr_name) = &args[0] {
                            let bin_op = &op[..1];
                            return Ok(AwkExpr::CompoundArrayAssign(
                                arr_name.clone(),
                                Box::new(args[1].clone()),
                                bin_op.to_string(),
                                Box::new(value),
                            ));
                        }
                        return Err(Error::Execution(
                            "awk: invalid assignment target".to_string(),
                        ));
                    }
                    AwkExpr::Field(index) => {
                        let bin_op = &op[..1];
                        let current = AwkExpr::Field(index.clone());
                        let combined =
                            AwkExpr::BinOp(Box::new(current), bin_op.to_string(), Box::new(value));
                        return Ok(AwkExpr::FieldAssign(index.clone(), Box::new(combined)));
                    }
                    _ => {
                        return Err(Error::Execution(
                            "awk: invalid assignment target".to_string(),
                        ));
                    }
                }
            }
        }

        // Simple assignment
        if self.current_char().unwrap() == '=' {
            let next = self.input[self.pos..].chars().nth(1);
            if next != Some('=') && next != Some('~') {
                self.pos += 1;
                self.skip_whitespace();
                let value = self.parse_assignment()?;

                match expr {
                    AwkExpr::Variable(name) => {
                        return Ok(AwkExpr::Assign(name, Box::new(value)));
                    }
                    AwkExpr::FuncCall(ref fname, ref args)
                        if fname == "__array_access" && args.len() == 2 =>
                    {
                        if let AwkExpr::Variable(arr_name) = &args[0] {
                            return Ok(AwkExpr::ArrayAssign(
                                arr_name.clone(),
                                Box::new(args[1].clone()),
                                Box::new(value),
                            ));
                        }
                        return Err(Error::Execution(
                            "awk: invalid assignment target".to_string(),
                        ));
                    }
                    AwkExpr::Field(index) => {
                        return Ok(AwkExpr::FieldAssign(index, Box::new(value)));
                    }
                    _ => {
                        return Err(Error::Execution(
                            "awk: invalid assignment target".to_string(),
                        ));
                    }
                }
            }
        }

        Ok(expr)
    }

    fn parse_ternary(&mut self) -> Result<AwkExpr> {
        let expr = self.parse_or()?;

        self.skip_whitespace();
        if self.pos < self.input.len() && self.current_char().unwrap() == '?' {
            self.pos += 1;
            self.skip_whitespace();
            let then_expr = self.parse_expression()?;
            self.skip_whitespace();
            if self.pos < self.input.len() && self.current_char().unwrap() == ':' {
                self.pos += 1;
                self.skip_whitespace();
                let else_expr = self.parse_expression()?;
                // Encode ternary as a function call for evaluation
                return Ok(AwkExpr::FuncCall(
                    "__ternary".to_string(),
                    vec![expr, then_expr, else_expr],
                ));
            }
        }

        Ok(expr)
    }

    fn parse_or(&mut self) -> Result<AwkExpr> {
        let mut left = self.parse_and()?;

        loop {
            self.skip_whitespace();
            if self.input[self.pos..].starts_with("||") {
                self.pos += 2;
                self.skip_whitespace();
                let right = self.parse_and()?;
                left = AwkExpr::BinOp(Box::new(left), "||".to_string(), Box::new(right));
            } else {
                break;
            }
        }

        Ok(left)
    }

    fn parse_and(&mut self) -> Result<AwkExpr> {
        let mut left = self.parse_comparison()?;

        loop {
            self.skip_whitespace();
            if self.input[self.pos..].starts_with("&&") {
                self.pos += 2;
                self.skip_whitespace();
                let right = self.parse_comparison()?;
                left = AwkExpr::BinOp(Box::new(left), "&&".to_string(), Box::new(right));
            } else {
                break;
            }
        }

        Ok(left)
    }

    fn parse_comparison(&mut self) -> Result<AwkExpr> {
        let left = self.parse_concat()?;

        self.skip_whitespace();

        // Check for `in` operator: (key in arr)
        if self.matches_keyword("in") {
            self.skip_whitespace();
            let start = self.pos;
            self.consume_while(Self::is_identifier_continue);
            let arr_name = self.input[start..self.pos].to_string();
            return Ok(AwkExpr::InArray(Box::new(left), arr_name));
        }

        // In print context, `>` and `>>` are output redirection, not comparison.
        // `>=` remains a comparison operator even in print context.
        let ops: &[&str] = if self.in_print_context {
            &["==", "!=", "<=", ">=", "<", "~", "!~"]
        } else {
            &["==", "!=", "<=", ">=", "<", ">", "~", "!~"]
        };

        for op in ops {
            if self.input[self.pos..].starts_with(op) {
                self.pos += op.len();
                self.skip_whitespace();
                let right = self.parse_concat()?;
                return Ok(AwkExpr::BinOp(
                    Box::new(left),
                    op.to_string(),
                    Box::new(right),
                ));
            }
        }

        Ok(left)
    }

    fn is_keyword_at_pos(&self) -> bool {
        let remaining = &self.input[self.pos..];
        let keywords = [
            "in", "if", "else", "while", "for", "do", "break", "continue", "next", "exit",
            "return", "delete", "getline", "print", "printf", "function",
        ];
        for kw in keywords {
            if remaining.starts_with(kw) {
                let after = self.pos + kw.len();
                if after >= self.input.len() || {
                    let c = self.input[after..].chars().next().unwrap();
                    !c.is_alphanumeric() && c != '_'
                } {
                    return true;
                }
            }
        }
        false
    }

    fn parse_concat(&mut self) -> Result<AwkExpr> {
        let mut parts = vec![self.parse_additive()?];

        loop {
            self.skip_whitespace();
            if self.pos >= self.input.len() {
                break;
            }

            let c = self.current_char().unwrap();
            // Check if this could be the start of another value for concatenation
            if c == '"' || c == '$' || Self::is_identifier_start(c) || c == '(' {
                // But not if it's a keyword or operator
                let remaining = &self.input[self.pos..];
                if !remaining.starts_with("||")
                    && !remaining.starts_with("&&")
                    && !remaining.starts_with("==")
                    && !remaining.starts_with("!=")
                    && !self.is_keyword_at_pos()
                    && let Ok(next) = self.parse_additive()
                {
                    parts.push(next);
                    continue;
                }
            }
            break;
        }

        if parts.len() == 1 {
            Ok(parts.remove(0))
        } else {
            Ok(AwkExpr::Concat(parts))
        }
    }

    fn parse_additive(&mut self) -> Result<AwkExpr> {
        let mut left = self.parse_multiplicative()?;

        loop {
            self.skip_whitespace();
            if self.pos >= self.input.len() {
                break;
            }

            let c = self.current_char().unwrap();
            if c == '+' || c == '-' {
                // Don't consume if it's a compound assignment operator (+=, -=)
                let next = self.input[self.pos..].chars().nth(1);
                if next == Some('=') {
                    break;
                }
                self.pos += 1;
                self.skip_whitespace();
                let right = self.parse_multiplicative()?;
                left = AwkExpr::BinOp(Box::new(left), c.to_string(), Box::new(right));
            } else {
                break;
            }
        }

        Ok(left)
    }

    fn parse_multiplicative(&mut self) -> Result<AwkExpr> {
        let mut left = self.parse_power()?;

        loop {
            self.skip_whitespace();
            if self.pos >= self.input.len() {
                break;
            }

            let c = self.current_char().unwrap();
            if c == '*' || c == '/' || c == '%' {
                // Don't consume ** (power operator)
                if c == '*' && self.input[self.pos..].chars().nth(1) == Some('*') {
                    break;
                }
                // Don't consume if it's a compound assignment operator (*=, /=, %=)
                let next = self.input[self.pos..].chars().nth(1);
                if next == Some('=') {
                    break;
                }
                self.pos += 1;
                self.skip_whitespace();
                let right = self.parse_power()?;
                left = AwkExpr::BinOp(Box::new(left), c.to_string(), Box::new(right));
            } else {
                break;
            }
        }

        Ok(left)
    }

    fn parse_power(&mut self) -> Result<AwkExpr> {
        let base = self.parse_unary()?;

        self.skip_whitespace();
        if self.pos >= self.input.len() {
            return Ok(base);
        }

        // Check for ^ or **
        if self.current_char().unwrap() == '^' {
            self.pos += 1;
            self.skip_whitespace();
            let exp = self.parse_unary()?;
            return Ok(AwkExpr::BinOp(
                Box::new(base),
                "^".to_string(),
                Box::new(exp),
            ));
        }
        if self.input[self.pos..].starts_with("**") {
            self.pos += 2;
            self.skip_whitespace();
            let exp = self.parse_unary()?;
            return Ok(AwkExpr::BinOp(
                Box::new(base),
                "^".to_string(),
                Box::new(exp),
            ));
        }

        Ok(base)
    }

    /// THREAT[TM-DOS-027]: Track depth on unary self-recursion
    fn parse_unary(&mut self) -> Result<AwkExpr> {
        self.skip_whitespace();

        if self.pos >= self.input.len() {
            return Err(Error::Execution(
                "awk: unexpected end of expression".to_string(),
            ));
        }

        // Pre-increment: ++var or ++arr[key]
        if self.input[self.pos..].starts_with("++") {
            self.pos += 2;
            self.skip_whitespace();
            match self.parse_primary()? {
                AwkExpr::Variable(name) => return Ok(AwkExpr::PreIncrement(name)),
                AwkExpr::FuncCall(ref fname, ref args)
                    if fname == "__array_access" && args.len() == 2 =>
                {
                    if let AwkExpr::Variable(arr_name) = &args[0] {
                        return Ok(AwkExpr::CompoundArrayAssign(
                            arr_name.clone(),
                            Box::new(args[1].clone()),
                            "+".to_string(),
                            Box::new(AwkExpr::Number(1.0)),
                        ));
                    }
                    return Err(Error::Execution(
                        "awk: expected variable after ++".to_string(),
                    ));
                }
                _ => {
                    return Err(Error::Execution(
                        "awk: expected variable after ++".to_string(),
                    ));
                }
            }
        }

        // Pre-decrement: --var or --arr[key]
        if self.input[self.pos..].starts_with("--") {
            self.pos += 2;
            self.skip_whitespace();
            match self.parse_primary()? {
                AwkExpr::Variable(name) => return Ok(AwkExpr::PreDecrement(name)),
                AwkExpr::FuncCall(ref fname, ref args)
                    if fname == "__array_access" && args.len() == 2 =>
                {
                    if let AwkExpr::Variable(arr_name) = &args[0] {
                        return Ok(AwkExpr::CompoundArrayAssign(
                            arr_name.clone(),
                            Box::new(args[1].clone()),
                            "-".to_string(),
                            Box::new(AwkExpr::Number(1.0)),
                        ));
                    }
                    return Err(Error::Execution(
                        "awk: expected variable after --".to_string(),
                    ));
                }
                _ => {
                    return Err(Error::Execution(
                        "awk: expected variable after --".to_string(),
                    ));
                }
            }
        }

        let c = self.current_char().unwrap();

        if c == '-' {
            self.pos += 1;
            self.push_depth()?;
            let expr = self.parse_unary();
            self.pop_depth();
            return Ok(AwkExpr::UnaryOp("-".to_string(), Box::new(expr?)));
        }

        if c == '!' {
            self.pos += 1;
            self.push_depth()?;
            let expr = self.parse_unary();
            self.pop_depth();
            return Ok(AwkExpr::UnaryOp("!".to_string(), Box::new(expr?)));
        }

        if c == '+' {
            self.pos += 1;
            self.push_depth()?;
            let result = self.parse_unary();
            self.pop_depth();
            return result;
        }

        self.parse_postfix()
    }

    fn parse_postfix(&mut self) -> Result<AwkExpr> {
        let expr = self.parse_primary()?;

        // Check for postfix ++ / --
        if self.pos + 1 < self.input.len() {
            if self.input[self.pos..].starts_with("++") {
                match &expr {
                    AwkExpr::Variable(name) => {
                        self.pos += 2;
                        return Ok(AwkExpr::PostIncrement(name.clone()));
                    }
                    AwkExpr::FuncCall(fname, args)
                        if fname == "__array_access" && args.len() == 2 =>
                    {
                        // arr[key]++ → compound array assign with +1
                        if let AwkExpr::Variable(arr_name) = &args[0] {
                            self.pos += 2;
                            return Ok(AwkExpr::CompoundArrayAssign(
                                arr_name.clone(),
                                Box::new(args[1].clone()),
                                "+".to_string(),
                                Box::new(AwkExpr::Number(1.0)),
                            ));
                        }
                    }
                    _ => {}
                }
            }
            if self.input[self.pos..].starts_with("--") {
                match &expr {
                    AwkExpr::Variable(name) => {
                        self.pos += 2;
                        return Ok(AwkExpr::PostDecrement(name.clone()));
                    }
                    AwkExpr::FuncCall(fname, args)
                        if fname == "__array_access" && args.len() == 2 =>
                    {
                        if let AwkExpr::Variable(arr_name) = &args[0] {
                            self.pos += 2;
                            return Ok(AwkExpr::CompoundArrayAssign(
                                arr_name.clone(),
                                Box::new(args[1].clone()),
                                "-".to_string(),
                                Box::new(AwkExpr::Number(1.0)),
                            ));
                        }
                    }
                    _ => {}
                }
            }
        }

        Ok(expr)
    }

    fn parse_primary(&mut self) -> Result<AwkExpr> {
        self.skip_whitespace();

        if self.pos >= self.input.len() {
            return Err(Error::Execution(
                "awk: unexpected end of expression".to_string(),
            ));
        }

        let c = self.current_char().unwrap();

        // Field reference $
        if c == '$' {
            self.pos += 1;
            self.push_depth()?;
            let index = self.parse_primary()?;
            self.pop_depth();
            return Ok(AwkExpr::Field(Box::new(index)));
        }

        // Number
        if c.is_ascii_digit() || c == '.' {
            return self.parse_number();
        }

        // String
        if c == '"' {
            let s = self.parse_string()?;
            return Ok(AwkExpr::String(s));
        }

        // Regex literal /pattern/
        if c == '/' {
            self.pos += 1;
            let start = self.pos;
            while self.pos < self.input.len() {
                let c = self.current_char().unwrap();
                if c == '/' {
                    let pattern = &self.input[start..self.pos];
                    self.pos += 1;
                    return Ok(AwkExpr::Regex(pattern.to_string()));
                } else if c == '\\' {
                    self.pos += 1; // skip '\\' (ASCII)
                    self.advance(); // skip next char (may be multi-byte)
                } else {
                    self.advance(); // regex content may be multi-byte
                }
            }
            return Err(Error::Execution("awk: unterminated regex".to_string()));
        }

        // Parenthesized expression: `>` inside parens is comparison, not redirection
        if c == '(' {
            self.pos += 1;
            self.push_depth()?;
            let saved_print_ctx = self.in_print_context;
            self.in_print_context = false;
            let expr = self.parse_expression()?;
            self.in_print_context = saved_print_ctx;
            self.pop_depth();
            self.skip_whitespace();
            if self.pos >= self.input.len() || self.current_char().unwrap() != ')' {
                return Err(Error::Execution("awk: expected ')'".to_string()));
            }
            self.pos += 1;
            return Ok(expr);
        }

        // Variable or function call
        if Self::is_identifier_start(c) {
            let start = self.pos;
            self.advance();
            self.consume_while(Self::is_identifier_continue);
            let name = self.input[start..self.pos].to_string();

            // getline [var] [< file] as expression (returns 1/0/-1)
            if name == "getline" {
                return self.parse_getline_expr();
            }

            self.skip_whitespace();
            if self.pos < self.input.len() && self.current_char().unwrap() == '(' {
                // Function call
                self.pos += 1;
                let mut args = Vec::new();
                loop {
                    self.skip_whitespace();
                    if self.pos < self.input.len() && self.current_char().unwrap() == ')' {
                        self.pos += 1;
                        break;
                    }
                    let arg = self.parse_expression()?;
                    args.push(arg);
                    self.skip_whitespace();
                    if self.pos < self.input.len() && self.current_char().unwrap() == ',' {
                        self.pos += 1;
                    }
                }
                return Ok(AwkExpr::FuncCall(name, args));
            }

            // Array indexing: arr[index] or arr[e1,e2,...] (multi-subscript with SUBSEP)
            if self.pos < self.input.len() && self.current_char().unwrap() == '[' {
                self.pos += 1; // consume '['
                let mut subscripts = vec![self.parse_expression()?];
                self.skip_whitespace();
                // Handle multi-subscript: arr[e1, e2, ...] joined by SUBSEP
                while self.pos < self.input.len() && self.current_char().unwrap() == ',' {
                    self.pos += 1; // consume ','
                    self.skip_whitespace();
                    subscripts.push(self.parse_expression()?);
                    self.skip_whitespace();
                }
                if self.pos >= self.input.len() || self.current_char().unwrap() != ']' {
                    return Err(Error::Execution("awk: expected ']'".to_string()));
                }
                self.pos += 1; // consume ']'
                let index_expr = if subscripts.len() == 1 {
                    subscripts.remove(0)
                } else {
                    // Join multiple subscripts with SUBSEP
                    let mut result = subscripts.remove(0);
                    for sub in subscripts {
                        result = AwkExpr::BinOp(
                            Box::new(result),
                            "SUBSEP_CONCAT".to_string(),
                            Box::new(sub),
                        );
                    }
                    result
                };
                return Ok(AwkExpr::FuncCall(
                    "__array_access".to_string(),
                    vec![AwkExpr::Variable(name), index_expr],
                ));
            }

            return Ok(AwkExpr::Variable(name));
        }

        Err(Error::Execution(format!(
            "awk: unexpected character: {}",
            c
        )))
    }

    fn parse_number(&mut self) -> Result<AwkExpr> {
        let start = self.pos;
        while self.pos < self.input.len() {
            let c = self.current_char().unwrap();
            if c.is_ascii_digit() || c == '.' || c == 'e' || c == 'E' || c == '-' || c == '+' {
                self.pos += 1;
            } else {
                break;
            }
        }

        let num_str = &self.input[start..self.pos];
        let num: f64 = num_str
            .parse()
            .map_err(|_| Error::Execution(format!("awk: invalid number: {}", num_str)))?;

        Ok(AwkExpr::Number(num))
    }

    fn parse_string(&mut self) -> Result<String> {
        if self.pos >= self.input.len() || self.current_char().unwrap() != '"' {
            return Err(Error::Execution("awk: expected string".to_string()));
        }
        self.pos += 1; // skip opening '"' (ASCII)

        let mut result = String::new();
        while self.pos < self.input.len() {
            let c = self.current_char().unwrap();
            if c == '"' {
                self.pos += 1; // skip closing '"' (ASCII)
                return Ok(result);
            } else if c == '\\' {
                self.pos += 1; // skip '\\' (ASCII)
                if self.pos < self.input.len() {
                    let escaped = self.current_char().unwrap();
                    match escaped {
                        'n' => {
                            result.push('\n');
                            self.advance();
                        }
                        't' => {
                            result.push('\t');
                            self.advance();
                        }
                        'r' => {
                            result.push('\r');
                            self.advance();
                        }
                        '\\' => {
                            result.push('\\');
                            self.advance();
                        }
                        '"' => {
                            result.push('"');
                            self.advance();
                        }
                        'u' => {
                            // gawk 5.3+ Unicode escape: \u followed by 1-8 hex digits
                            self.advance(); // skip 'u'
                            let mut hex = String::new();
                            while hex.len() < 8
                                && self.pos < self.input.len()
                                && self.current_char().is_some_and(|c| c.is_ascii_hexdigit())
                            {
                                hex.push(self.current_char().unwrap());
                                self.pos += 1; // hex digits are ASCII
                            }
                            if hex.is_empty() {
                                result.push('\\');
                                result.push('u');
                            } else if let Ok(cp) = u32::from_str_radix(&hex, 16) {
                                if let Some(ch) = char::from_u32(cp) {
                                    result.push(ch);
                                } else {
                                    // Invalid Unicode code point — keep literal
                                    result.push('\\');
                                    result.push('u');
                                    result.push_str(&hex);
                                }
                            } else {
                                result.push('\\');
                                result.push('u');
                                result.push_str(&hex);
                            }
                        }
                        _ => {
                            result.push('\\');
                            result.push(escaped);
                            self.advance();
                        }
                    }
                }
            } else {
                result.push(c);
                self.advance(); // character may be multi-byte
            }
        }

        Err(Error::Execution("awk: unterminated string".to_string()))
    }
}

/// Flow control signal from action execution
#[derive(Debug, PartialEq)]
enum AwkFlow {
    Continue,          // Normal execution
    Next,              // Skip to next record
    Break,             // Break out of loop
    LoopContinue,      // Continue to next loop iteration
    Exit(Option<i32>), // Exit program with optional code
    Return(AwkValue),  // Return from user-defined function
}

/// THREAT[TM-DOS-027]: Maximum recursion depth for awk user-defined function calls.
const MAX_AWK_CALL_DEPTH: usize = 64;

/// THREAT[TM-DOS-027]: Maximum total AWK output size (stdout + stderr + file redirects)
/// to prevent memory exhaustion. 10 MB.
const MAX_AWK_OUTPUT_BYTES: usize = 10_000_000;

/// THREAT[TM-DOS-028]: Maximum number of distinct files cached by `getline var < file`.
/// Prevents memory exhaustion from opening hundreds of large files.
const MAX_GETLINE_CACHED_FILES: usize = 100;

struct AwkInterpreter {
    state: AwkState,
    output: String,
    /// Stderr output buffer for `/dev/stderr` redirection
    stderr_output: String,
    /// Lines of current input file (set before main loop)
    input_lines: Vec<String>,
    /// Current line index within input_lines
    line_index: usize,
    /// User-defined functions
    functions: HashMap<String, AwkFunctionDef>,
    /// Current function call depth for recursion limiting
    call_depth: usize,
    /// Buffered file outputs for `>` redirection (path -> content).
    /// Truncate mode: first write creates entry, subsequent writes append to buffer.
    /// Flushed to VFS after execution completes.
    file_outputs: HashMap<String, String>,
    /// Buffered file outputs for `>>` redirection (path -> content).
    /// Append mode: content is appended to existing file on flush.
    file_appends: HashMap<String, String>,
    /// Cached file inputs for `getline var < file` redirection.
    /// Maps resolved path -> (lines, current_position).
    file_inputs: HashMap<String, (Vec<String>, usize)>,
    /// VFS reference for lazy file reads (getline < file).
    fs: Option<Arc<dyn FileSystem>>,
    /// Working directory for resolving relative paths.
    cwd: PathBuf,
    /// Tracks active state for range patterns (rule index -> is_active).
    /// When start pattern matches, range becomes active. When end pattern
    /// matches, that line is included but range becomes inactive.
    range_active: HashMap<usize, bool>,
    /// Max iterations for a single loop, inherited from execution limits.
    max_loop_iterations: usize,
}

impl AwkInterpreter {
    fn new() -> Self {
        Self {
            state: AwkState::default(),
            output: String::new(),
            stderr_output: String::new(),
            input_lines: Vec::new(),
            line_index: 0,
            functions: HashMap::new(),
            file_outputs: HashMap::new(),
            file_appends: HashMap::new(),
            file_inputs: HashMap::new(),
            call_depth: 0,
            fs: None,
            cwd: PathBuf::from("/"),
            range_active: HashMap::new(),
            max_loop_iterations: ExecutionLimits::default().max_loop_iterations,
        }
    }

    /// Load a file into the `file_inputs` cache if not already present.
    /// Uses a separate thread + tokio runtime to bridge async VFS → sync context.
    /// Returns true on success, false on error.
    fn ensure_file_loaded(&mut self, resolved: &str) -> bool {
        if self.file_inputs.contains_key(resolved) {
            return true;
        }
        // Guard: cap number of cached files to prevent memory exhaustion
        if self.file_inputs.len() >= MAX_GETLINE_CACHED_FILES {
            return false;
        }
        let Some(fs) = &self.fs else {
            return false;
        };
        let fs = fs.clone();
        let p = PathBuf::from(resolved);
        // Spawn a thread with its own runtime to avoid blocking the current async runtime.
        let result = std::thread::spawn(move || {
            tokio::runtime::Builder::new_current_thread()
                .enable_all()
                .build()
                .unwrap()
                .block_on(fs.read_file(&p))
        })
        .join();
        match result {
            Ok(Ok(bytes)) => {
                let text = String::from_utf8_lossy(&bytes).into_owned();
                let lines: Vec<String> = text.lines().map(|l| l.to_string()).collect();
                self.file_inputs.insert(resolved.to_string(), (lines, 0));
                true
            }
            _ => false,
        }
    }

    /// Evaluate an expression as a boolean, with special handling for regex
    /// literals: `/regex/` is matched against $0 in boolean context (e.g. && / ||).
    fn eval_expr_as_bool(&mut self, expr: &AwkExpr) -> bool {
        if let AwkExpr::Regex(pattern) = expr {
            let line = self.state.get_field(0).as_string();
            if let Ok(re) = build_regex(pattern) {
                return re.is_match(&line);
            }
            return false;
        }
        self.eval_expr(expr).as_bool()
    }

    fn eval_expr(&mut self, expr: &AwkExpr) -> AwkValue {
        match expr {
            AwkExpr::Number(n) => AwkValue::Number(*n),
            AwkExpr::String(s) => AwkValue::String(s.clone()),
            AwkExpr::Field(index) => {
                let n = self.eval_expr(index).as_number() as usize;
                self.state.get_field(n)
            }
            AwkExpr::Variable(name) => self.state.get_variable(name),
            AwkExpr::Assign(name, val) => {
                let value = self.eval_expr(val);
                self.state.set_variable(name, value.clone());
                value
            }
            AwkExpr::BinOp(left, op, right) => {
                if op == "&&" {
                    let lb = self.eval_expr_as_bool(left);
                    if !lb {
                        return AwkValue::Number(0.0);
                    }
                    let rb = self.eval_expr_as_bool(right);
                    return AwkValue::Number(if rb { 1.0 } else { 0.0 });
                }
                if op == "||" {
                    let lb = self.eval_expr_as_bool(left);
                    if lb {
                        return AwkValue::Number(1.0);
                    }
                    let rb = self.eval_expr_as_bool(right);
                    return AwkValue::Number(if rb { 1.0 } else { 0.0 });
                }

                let l = self.eval_expr(left);
                let r = self.eval_expr(right);

                match op.as_str() {
                    "+" => AwkValue::Number(l.as_number() + r.as_number()),
                    "-" => AwkValue::Number(l.as_number() - r.as_number()),
                    "*" => AwkValue::Number(l.as_number() * r.as_number()),
                    "/" => AwkValue::Number(l.as_number() / r.as_number()),
                    "%" => AwkValue::Number(l.as_number() % r.as_number()),
                    "^" => AwkValue::Number(l.as_number().powf(r.as_number())),
                    "==" => AwkValue::Number(if l.as_string() == r.as_string() {
                        1.0
                    } else {
                        0.0
                    }),
                    "!=" => AwkValue::Number(if l.as_string() != r.as_string() {
                        1.0
                    } else {
                        0.0
                    }),
                    "<" => AwkValue::Number(if l.as_number() < r.as_number() {
                        1.0
                    } else {
                        0.0
                    }),
                    ">" => AwkValue::Number(if l.as_number() > r.as_number() {
                        1.0
                    } else {
                        0.0
                    }),
                    "<=" => AwkValue::Number(if l.as_number() <= r.as_number() {
                        1.0
                    } else {
                        0.0
                    }),
                    ">=" => AwkValue::Number(if l.as_number() >= r.as_number() {
                        1.0
                    } else {
                        0.0
                    }),
                    "~" => {
                        if let Ok(re) = build_regex(&r.as_string()) {
                            AwkValue::Number(if re.is_match(&l.as_string()) {
                                1.0
                            } else {
                                0.0
                            })
                        } else {
                            AwkValue::Number(0.0)
                        }
                    }
                    "!~" => {
                        if let Ok(re) = build_regex(&r.as_string()) {
                            AwkValue::Number(if !re.is_match(&l.as_string()) {
                                1.0
                            } else {
                                0.0
                            })
                        } else {
                            AwkValue::Number(1.0)
                        }
                    }
                    "SUBSEP_CONCAT" => {
                        let subsep = self.state.get_variable("SUBSEP").as_string();
                        AwkValue::String(format!("{}{}{}", l.as_string(), subsep, r.as_string()))
                    }
                    _ => AwkValue::Uninitialized,
                }
            }
            AwkExpr::UnaryOp(op, expr) => match op.as_str() {
                "-" => {
                    let v = self.eval_expr(expr);
                    AwkValue::Number(-v.as_number())
                }
                "!" => {
                    let b = self.eval_expr_as_bool(expr);
                    AwkValue::Number(if b { 0.0 } else { 1.0 })
                }
                _ => self.eval_expr(expr),
            },
            AwkExpr::Concat(parts) => {
                let s: String = parts
                    .iter()
                    .map(|p| self.eval_expr(p).as_string())
                    .collect();
                AwkValue::String(s)
            }
            AwkExpr::ArrayAssign(name, key, val) => {
                let k = self.eval_expr(key).as_string();
                let v = self.eval_expr(val);
                let full_key = format!("{}[{}]", name, k);
                self.state.set_variable(&full_key, v.clone());
                v
            }
            AwkExpr::CompoundArrayAssign(name, key, op, val) => {
                let k = self.eval_expr(key).as_string();
                let full_key = format!("{}[{}]", name, k);
                let current = self.state.get_variable(&full_key).as_number();
                let rhs = self.eval_expr(val).as_number();
                let result = match op.as_str() {
                    "+" => current + rhs,
                    "-" => current - rhs,
                    "*" => current * rhs,
                    "/" => current / rhs,
                    "%" => current % rhs,
                    _ => rhs,
                };
                let v = AwkValue::Number(result);
                self.state.set_variable(&full_key, v.clone());
                v
            }
            AwkExpr::FieldAssign(index, val) => {
                let n = self.eval_expr(index).as_number() as usize;
                let v = self.eval_expr(val);
                if n == 0 {
                    self.state.set_variable("$0", v.clone());
                } else {
                    // Extend fields if needed
                    while self.state.fields.len() < n {
                        self.state.fields.push(String::new());
                    }
                    self.state.fields[n - 1] = v.as_string();
                    self.state.nf = self.state.fields.len();
                    // Rebuild $0
                    let new_line = self.state.fields.join(&self.state.ofs);
                    self.state.set_variable("$0", AwkValue::String(new_line));
                }
                v
            }
            AwkExpr::PostIncrement(name) => {
                let current = self.state.get_variable(name).as_number();
                self.state
                    .set_variable(name, AwkValue::Number(current + 1.0));
                AwkValue::Number(current) // Return old value
            }
            AwkExpr::PostDecrement(name) => {
                let current = self.state.get_variable(name).as_number();
                self.state
                    .set_variable(name, AwkValue::Number(current - 1.0));
                AwkValue::Number(current) // Return old value
            }
            AwkExpr::PreIncrement(name) => {
                let current = self.state.get_variable(name).as_number();
                let new_val = current + 1.0;
                self.state.set_variable(name, AwkValue::Number(new_val));
                AwkValue::Number(new_val) // Return new value
            }
            AwkExpr::PreDecrement(name) => {
                let current = self.state.get_variable(name).as_number();
                let new_val = current - 1.0;
                self.state.set_variable(name, AwkValue::Number(new_val));
                AwkValue::Number(new_val) // Return new value
            }
            AwkExpr::InArray(key, arr_name) => {
                let k = self.eval_expr(key).as_string();
                let full_key = format!("{}[{}]", arr_name, k);
                let exists = !matches!(self.state.get_variable(&full_key), AwkValue::Uninitialized);
                AwkValue::Number(if exists { 1.0 } else { 0.0 })
            }
            AwkExpr::FuncCall(name, args) => self.call_function(name, args),
            AwkExpr::Regex(pattern) => {
                // When used as a standalone expression, /regex/ matches against $0.
                // When used as a function argument (gsub, sub, match, split),
                // it's evaluated as a string pattern, so return the pattern string.
                AwkValue::String(pattern.clone())
            }
            AwkExpr::Match(expr, pattern) => {
                let s = self.eval_expr(expr).as_string();
                if let Ok(re) = build_regex(pattern) {
                    AwkValue::Number(if re.is_match(&s) { 1.0 } else { 0.0 })
                } else {
                    AwkValue::Number(0.0)
                }
            }
            AwkExpr::GetlineFile { var, file } => {
                let path_str = self.eval_expr(file).as_string();
                let resolved = if path_str.starts_with('/') {
                    path_str.clone()
                } else {
                    self.cwd.join(&path_str).to_string_lossy().to_string()
                };

                if !self.ensure_file_loaded(&resolved) {
                    return AwkValue::Number(-1.0);
                }

                let entry = self.file_inputs.get_mut(&resolved).unwrap();
                if entry.1 < entry.0.len() {
                    let line = entry.0[entry.1].clone();
                    entry.1 += 1;
                    match var {
                        Some(v) => {
                            self.state
                                .variables
                                .insert(v.clone(), AwkValue::String(line));
                        }
                        None => {
                            self.state.set_line(&line);
                        }
                    }
                    AwkValue::Number(1.0) // success
                } else {
                    AwkValue::Number(0.0) // EOF
                }
            }
        }
    }

    fn call_function(&mut self, name: &str, args: &[AwkExpr]) -> AwkValue {
        match name {
            "length" => {
                if args.is_empty() {
                    AwkValue::Number(self.state.get_field(0).as_string().len() as f64)
                } else {
                    // Check if the argument is an array name - if so, return element count
                    if let AwkExpr::Variable(ref arr_name) = args[0] {
                        let prefix = format!("{}[", arr_name);
                        let count = self
                            .state
                            .variables
                            .keys()
                            .filter(|k| k.starts_with(&prefix))
                            .count();
                        if count > 0 {
                            return AwkValue::Number(count as f64);
                        }
                    }
                    AwkValue::Number(self.eval_expr(&args[0]).as_string().len() as f64)
                }
            }
            "substr" => {
                if args.len() < 2 {
                    return AwkValue::Uninitialized;
                }
                let s = self.eval_expr(&args[0]).as_string();
                let start = (self.eval_expr(&args[1]).as_number() as usize).saturating_sub(1);
                let len = if args.len() > 2 {
                    self.eval_expr(&args[2]).as_number() as usize
                } else {
                    s.len()
                };
                let end = (start + len).min(s.len());
                AwkValue::String(s.chars().skip(start).take(end - start).collect())
            }
            "index" => {
                if args.len() < 2 {
                    return AwkValue::Number(0.0);
                }
                let s = self.eval_expr(&args[0]).as_string();
                let t = self.eval_expr(&args[1]).as_string();
                match s.find(&t) {
                    Some(i) => AwkValue::Number((i + 1) as f64),
                    None => AwkValue::Number(0.0),
                }
            }
            "split" => {
                if args.len() < 2 {
                    return AwkValue::Number(0.0);
                }
                let s = self.eval_expr(&args[0]).as_string();
                let sep = if args.len() > 2 {
                    self.eval_expr(&args[2]).as_string()
                } else {
                    self.state.fs.clone()
                };

                let parts: Vec<&str> = if sep == " " {
                    s.split_whitespace().collect()
                } else {
                    s.split(&sep).collect()
                };

                // Store in array variable
                if let AwkExpr::Variable(arr_name) = &args[1] {
                    for (i, part) in parts.iter().enumerate() {
                        let key = format!("{}[{}]", arr_name, i + 1);
                        self.state
                            .set_variable(&key, AwkValue::String(part.to_string()));
                    }
                }

                AwkValue::Number(parts.len() as f64)
            }
            "sprintf" => {
                if args.is_empty() {
                    return AwkValue::String(String::new());
                }
                let format = self.eval_expr(&args[0]).as_string();
                let values: Vec<AwkValue> = args[1..].iter().map(|a| self.eval_expr(a)).collect();
                match self.format_string(&format, &values) {
                    Ok(s) => AwkValue::String(s),
                    Err(e) => {
                        self.stderr_output.push_str(&e);
                        self.stderr_output.push('\n');
                        AwkValue::String(String::new())
                    }
                }
            }
            "toupper" => {
                if args.is_empty() {
                    return AwkValue::Uninitialized;
                }
                AwkValue::String(self.eval_expr(&args[0]).as_string().to_uppercase())
            }
            "tolower" => {
                if args.is_empty() {
                    return AwkValue::Uninitialized;
                }
                AwkValue::String(self.eval_expr(&args[0]).as_string().to_lowercase())
            }
            "gsub" | "sub" => {
                // gsub(regexp, replacement, target)
                if args.len() < 2 {
                    return AwkValue::Number(0.0);
                }
                let pattern = self.eval_expr(&args[0]).as_string();
                let replacement = self.eval_expr(&args[1]).as_string();

                let target_expr = if args.len() > 2 {
                    args[2].clone()
                } else {
                    AwkExpr::Field(Box::new(AwkExpr::Number(0.0)))
                };

                let target = self.eval_expr(&target_expr).as_string();

                if let Ok(re) = build_regex(&pattern) {
                    let (result, count) = if name == "gsub" {
                        let count = re.find_iter(&target).count();
                        (
                            re.replace_all(&target, replacement.as_str()).to_string(),
                            count,
                        )
                    } else {
                        let count = if re.is_match(&target) { 1 } else { 0 };
                        (re.replace(&target, replacement.as_str()).to_string(), count)
                    };

                    // Update the target variable or field
                    match &target_expr {
                        AwkExpr::Variable(name) => {
                            self.state.set_variable(name, AwkValue::String(result));
                        }
                        AwkExpr::Field(index) => {
                            let n = self.eval_expr(index).as_number() as usize;
                            if n == 0 {
                                // $0 is stored as a variable
                                self.state.set_variable("$0", AwkValue::String(result));
                            }
                            // For other fields, we'd need to update the fields vec
                            // and rebuild $0, but for now we just support $0
                        }
                        _ => {}
                    }

                    AwkValue::Number(count as f64)
                } else {
                    AwkValue::Number(0.0)
                }
            }
            "int" => {
                if args.is_empty() {
                    return AwkValue::Number(0.0);
                }
                AwkValue::Number(self.eval_expr(&args[0]).as_number().trunc())
            }
            "sqrt" => {
                if args.is_empty() {
                    return AwkValue::Number(0.0);
                }
                AwkValue::Number(self.eval_expr(&args[0]).as_number().sqrt())
            }
            "sin" => {
                if args.is_empty() {
                    return AwkValue::Number(0.0);
                }
                AwkValue::Number(self.eval_expr(&args[0]).as_number().sin())
            }
            "cos" => {
                if args.is_empty() {
                    return AwkValue::Number(0.0);
                }
                AwkValue::Number(self.eval_expr(&args[0]).as_number().cos())
            }
            "log" => {
                if args.is_empty() {
                    return AwkValue::Number(0.0);
                }
                AwkValue::Number(self.eval_expr(&args[0]).as_number().ln())
            }
            "exp" => {
                if args.is_empty() {
                    return AwkValue::Number(0.0);
                }
                AwkValue::Number(self.eval_expr(&args[0]).as_number().exp())
            }
            "match" => {
                if args.len() < 2 {
                    return AwkValue::Number(0.0);
                }
                let s = self.eval_expr(&args[0]).as_string();
                let pattern = self.eval_expr(&args[1]).as_string();
                // Extract capture array name from 3rd arg (gawk extension)
                let arr_name = if args.len() >= 3 {
                    if let AwkExpr::Variable(name) = &args[2] {
                        Some(name.clone())
                    } else {
                        None
                    }
                } else {
                    None
                };
                if let Ok(re) = build_regex(&pattern) {
                    if let Some(caps) = re.captures(&s) {
                        let m = caps.get(0).unwrap();
                        let rstart = m.start() + 1; // awk is 1-indexed
                        let rlength = m.end() - m.start();
                        self.state
                            .set_variable("RSTART", AwkValue::Number(rstart as f64));
                        self.state
                            .set_variable("RLENGTH", AwkValue::Number(rlength as f64));
                        // Populate capture array if 3rd arg provided
                        if let Some(ref arr) = arr_name {
                            // arr[0] = entire match
                            let full_key = format!("{}[0]", arr);
                            self.state
                                .set_variable(&full_key, AwkValue::String(m.as_str().to_string()));
                            // arr[1..N] = capture groups
                            for i in 1..caps.len() {
                                let key = format!("{}[{}]", arr, i);
                                let val = caps
                                    .get(i)
                                    .map(|c| c.as_str().to_string())
                                    .unwrap_or_default();
                                self.state.set_variable(&key, AwkValue::String(val));
                            }
                        }
                        AwkValue::Number(rstart as f64)
                    } else {
                        self.state.set_variable("RSTART", AwkValue::Number(0.0));
                        self.state.set_variable("RLENGTH", AwkValue::Number(-1.0));
                        AwkValue::Number(0.0)
                    }
                } else {
                    AwkValue::Number(0.0)
                }
            }
            "gensub" => {
                // gensub(regexp, replacement, how [, target])
                if args.len() < 3 {
                    return AwkValue::Uninitialized;
                }
                let pattern = self.eval_expr(&args[0]).as_string();
                let replacement = self.eval_expr(&args[1]).as_string();
                let how = self.eval_expr(&args[2]).as_string();
                let target = if args.len() > 3 {
                    self.eval_expr(&args[3]).as_string()
                } else {
                    self.state.get_field(0).as_string()
                };
                if let Ok(re) = build_regex(&pattern) {
                    if how == "g" || how == "G" {
                        AwkValue::String(re.replace_all(&target, replacement.as_str()).to_string())
                    } else {
                        // Replace nth occurrence (default 1st)
                        let n = how.parse::<usize>().unwrap_or(1);
                        let mut count = 0;
                        let result = re.replace_all(&target, |caps: &regex::Captures| -> String {
                            count += 1;
                            if count == n {
                                replacement.clone()
                            } else {
                                caps[0].to_string()
                            }
                        });
                        AwkValue::String(result.to_string())
                    }
                } else {
                    AwkValue::String(target)
                }
            }
            "__getline" => {
                // Plain getline as expression — advance to next input line, return 1/0
                self.line_index += 1;
                if self.line_index < self.input_lines.len() {
                    let line = self.input_lines[self.line_index].clone();
                    self.state.set_line(&line);
                    AwkValue::Number(1.0)
                } else {
                    AwkValue::Number(0.0)
                }
            }
            "__array_access" => {
                // Internal function for array indexing: arr[index]
                if args.len() < 2 {
                    return AwkValue::Uninitialized;
                }
                let arr_name = if let AwkExpr::Variable(name) = &args[0] {
                    name.clone()
                } else {
                    return AwkValue::Uninitialized;
                };
                let index = self.eval_expr(&args[1]);
                let key = format!("{}[{}]", arr_name, index.as_string());
                self.state.get_variable(&key)
            }
            "__ternary" => {
                // Ternary operator: cond ? then : else
                if args.len() < 3 {
                    return AwkValue::Uninitialized;
                }
                let cond = self.eval_expr(&args[0]);
                if cond.as_bool() {
                    self.eval_expr(&args[1])
                } else {
                    self.eval_expr(&args[2])
                }
            }
            _ => {
                // Check for user-defined function
                if let Some(func) = self.functions.get(name).cloned() {
                    self.call_user_function(&func, args)
                } else {
                    AwkValue::Uninitialized
                }
            }
        }
    }

    fn call_user_function(&mut self, func: &AwkFunctionDef, args: &[AwkExpr]) -> AwkValue {
        // THREAT[TM-DOS-027]: Limit recursion depth to prevent stack overflow
        if self.call_depth >= MAX_AWK_CALL_DEPTH {
            return AwkValue::Uninitialized;
        }
        self.call_depth += 1;

        // Save current local variables that will be shadowed
        let mut saved: Vec<(String, AwkValue)> = Vec::new();
        for param in &func.params {
            saved.push((param.clone(), self.state.get_variable(param)));
        }

        // Bind arguments to parameters
        for (i, param) in func.params.iter().enumerate() {
            let val = if i < args.len() {
                self.eval_expr(&args[i])
            } else {
                AwkValue::Uninitialized
            };
            self.state.set_variable(param, val);
        }

        // Execute function body, capture return value
        let mut return_value = AwkValue::Uninitialized;
        for action in &func.body.clone() {
            match self.exec_action(action) {
                AwkFlow::Return(val) => {
                    return_value = val;
                    break;
                }
                AwkFlow::Exit(_) => break,
                _ => {}
            }
        }

        // Restore saved variables
        for (name, val) in saved {
            self.state.set_variable(&name, val);
        }

        self.call_depth -= 1;
        return_value
    }

    /// Max width/precision for format specifiers to prevent memory exhaustion
    const MAX_FORMAT_WIDTH: usize = super::MAX_FORMAT_WIDTH;

    fn format_string(
        &self,
        format: &str,
        values: &[AwkValue],
    ) -> std::result::Result<String, String> {
        let mut result = String::new();
        let mut chars = format.chars().peekable();
        let mut value_idx = 0;

        while let Some(c) = chars.next() {
            if c == '\\' {
                // Handle escape sequences in format strings
                match chars.peek() {
                    Some('n') => {
                        chars.next();
                        result.push('\n');
                    }
                    Some('t') => {
                        chars.next();
                        result.push('\t');
                    }
                    Some('r') => {
                        chars.next();
                        result.push('\r');
                    }
                    Some('\\') => {
                        chars.next();
                        result.push('\\');
                    }
                    _ => result.push('\\'),
                }
            } else if c == '%' {
                if chars.peek() == Some(&'%') {
                    chars.next();
                    result.push('%');
                    continue;
                }

                // Parse format specifier: %[flags][width][.precision]type
                let mut left_align = false;
                let mut zero_pad = false;
                let mut plus_sign = false;
                let mut width: Option<usize> = None;
                let mut precision: Option<usize> = None;
                let mut conversion = ' ';

                // Parse flags
                loop {
                    match chars.peek() {
                        Some(&'-') => {
                            left_align = true;
                            chars.next();
                        }
                        Some(&'0') if width.is_none() => {
                            zero_pad = true;
                            chars.next();
                        }
                        Some(&'+') => {
                            plus_sign = true;
                            chars.next();
                        }
                        _ => break,
                    }
                }

                // Parse width
                let mut w = String::new();
                while let Some(&c) = chars.peek() {
                    if c.is_ascii_digit() {
                        w.push(c);
                        chars.next();
                    } else {
                        break;
                    }
                }
                if !w.is_empty()
                    && let Ok(w_val) = w.parse::<usize>()
                {
                    if w_val > Self::MAX_FORMAT_WIDTH {
                        return Err(format!(
                            "awk: format width {} exceeds maximum ({})",
                            w_val,
                            Self::MAX_FORMAT_WIDTH
                        ));
                    }
                    width = Some(w_val);
                }

                // Parse precision
                if chars.peek() == Some(&'.') {
                    chars.next();
                    let mut p = String::new();
                    while let Some(&c) = chars.peek() {
                        if c.is_ascii_digit() {
                            p.push(c);
                            chars.next();
                        } else {
                            break;
                        }
                    }
                    precision = if p.is_empty() {
                        Some(0)
                    } else if let Ok(p_val) = p.parse::<usize>() {
                        if p_val > Self::MAX_FORMAT_WIDTH {
                            return Err(format!(
                                "awk: format precision {} exceeds maximum ({})",
                                p_val,
                                Self::MAX_FORMAT_WIDTH
                            ));
                        }
                        Some(p_val)
                    } else {
                        None
                    };
                }

                // Parse conversion character
                if let Some(&c) = chars.peek()
                    && c.is_ascii_alphabetic()
                {
                    conversion = c;
                    chars.next();
                }

                if value_idx < values.len() {
                    let val = &values[value_idx];
                    value_idx += 1;

                    let formatted = match conversion {
                        'd' | 'i' => {
                            let n = val.as_number() as i64;
                            if plus_sign && n >= 0 {
                                format!("+{}", n)
                            } else {
                                format!("{}", n)
                            }
                        }
                        'f' => {
                            let n = val.as_number();
                            let prec = precision.unwrap_or(6);
                            format!("{:.prec$}", n)
                        }
                        'g' => {
                            let n = val.as_number();
                            let prec = precision.unwrap_or(6);
                            // %g: use shorter of %e or %f, strip trailing zeros
                            let s = format!("{:.prec$e}", n);
                            let f = format!("{:.prec$}", n);
                            if s.len() < f.len() { s } else { f }
                        }
                        'e' | 'E' => {
                            let n = val.as_number();
                            let prec = precision.unwrap_or(6);
                            format!("{:.prec$e}", n)
                        }
                        's' => {
                            let mut s = val.as_string();
                            if let Some(p) = precision {
                                s = s.chars().take(p).collect();
                            }
                            s
                        }
                        'c' => {
                            // %c: print character from ASCII code or first char of string
                            let n = val.as_number();
                            if n > 0.0 && n < 128.0 {
                                String::from(n as u8 as char)
                            } else {
                                let s = val.as_string();
                                s.chars().next().map(String::from).unwrap_or_default()
                            }
                        }
                        'x' | 'X' => {
                            let n = val.as_number() as i64;
                            if conversion == 'X' {
                                format!("{:X}", n)
                            } else {
                                format!("{:x}", n)
                            }
                        }
                        'o' => {
                            let n = val.as_number() as i64;
                            format!("{:o}", n)
                        }
                        _ => val.as_string(),
                    };

                    // Apply width and alignment
                    if let Some(w) = width {
                        if formatted.len() < w {
                            let padding = w - formatted.len();
                            if left_align {
                                result.push_str(&formatted);
                                for _ in 0..padding {
                                    result.push(' ');
                                }
                            } else if zero_pad
                                && matches!(conversion, 'd' | 'i' | 'f' | 'x' | 'X' | 'o')
                            {
                                for _ in 0..padding {
                                    result.push('0');
                                }
                                result.push_str(&formatted);
                            } else {
                                for _ in 0..padding {
                                    result.push(' ');
                                }
                                result.push_str(&formatted);
                            }
                        } else {
                            result.push_str(&formatted);
                        }
                    } else {
                        result.push_str(&formatted);
                    }
                }
            } else {
                result.push(c);
            }
        }

        Ok(result)
    }

    /// Total bytes buffered across all output streams.
    fn total_output_bytes(&self) -> usize {
        self.output.len()
            + self.stderr_output.len()
            + self.file_outputs.values().map(|v| v.len()).sum::<usize>()
            + self.file_appends.values().map(|v| v.len()).sum::<usize>()
    }

    /// Write text to stdout buffer or to a file output buffer based on the target.
    /// Returns `false` if the write would exceed [`MAX_AWK_OUTPUT_BYTES`].
    fn write_output(&mut self, text: &str, target: &Option<AwkOutputTarget>) -> bool {
        if self.total_output_bytes() + text.len() > MAX_AWK_OUTPUT_BYTES {
            self.stderr_output
                .push_str("awk: output limit exceeded (max 10MB)\n");
            return false;
        }
        match target {
            None => self.output.push_str(text),
            Some(AwkOutputTarget::Truncate(expr)) | Some(AwkOutputTarget::Append(expr)) => {
                let path = self.eval_expr(expr).as_string();
                // Intercept /dev/stderr and /dev/stdout — route to streams, not VFS
                if path == "/dev/stderr" {
                    self.stderr_output.push_str(text);
                } else if path == "/dev/stdout" {
                    self.output.push_str(text);
                } else if matches!(target, Some(AwkOutputTarget::Append(_))) {
                    self.file_appends.entry(path).or_default().push_str(text);
                } else {
                    self.file_outputs.entry(path).or_default().push_str(text);
                }
            }
        }
        true
    }

    /// Execute action. Returns flow control signal.
    fn exec_action(&mut self, action: &AwkAction) -> AwkFlow {
        match action {
            AwkAction::Print(exprs, target) => {
                let parts: Vec<String> = exprs
                    .iter()
                    .map(|e| self.eval_expr(e).as_string())
                    .collect();
                let mut text = parts.join(&self.state.ofs);
                text.push_str(&self.state.ors);
                if !self.write_output(&text, target) {
                    return AwkFlow::Exit(Some(2));
                }
                AwkFlow::Continue
            }
            AwkAction::Printf(format_expr, args, target) => {
                let format_str = self.eval_expr(format_expr).as_string();
                let values: Vec<AwkValue> = args.iter().map(|a| self.eval_expr(a)).collect();
                match self.format_string(&format_str, &values) {
                    Ok(text) => {
                        if !self.write_output(&text, target) {
                            return AwkFlow::Exit(Some(2));
                        }
                        AwkFlow::Continue
                    }
                    Err(e) => {
                        self.stderr_output.push_str(&e);
                        self.stderr_output.push('\n');
                        AwkFlow::Exit(Some(2))
                    }
                }
            }
            AwkAction::Assign(name, expr) => {
                let value = self.eval_expr(expr);
                self.state.set_variable(name, value);
                AwkFlow::Continue
            }
            AwkAction::ArrayAssign(name, key, val) => {
                let k = self.eval_expr(key).as_string();
                let v = self.eval_expr(val);
                let full_key = format!("{}[{}]", name, k);
                self.state.set_variable(&full_key, v);
                AwkFlow::Continue
            }
            AwkAction::If(cond, then_actions, else_actions) => {
                let actions = if self.eval_expr(cond).as_bool() {
                    then_actions
                } else {
                    else_actions
                };
                for action in actions {
                    match self.exec_action(action) {
                        AwkFlow::Continue => {}
                        flow => return flow,
                    }
                }
                AwkFlow::Continue
            }
            AwkAction::While(cond, actions) => {
                let mut iters = 0;
                while self.eval_expr(cond).as_bool() {
                    iters += 1;
                    if iters > self.max_loop_iterations {
                        break;
                    }
                    let mut do_break = false;
                    for action in actions {
                        match self.exec_action(action) {
                            AwkFlow::Continue => {}
                            AwkFlow::Break => {
                                do_break = true;
                                break;
                            }
                            AwkFlow::LoopContinue => break,
                            flow => return flow,
                        }
                    }
                    if do_break {
                        break;
                    }
                }
                AwkFlow::Continue
            }
            AwkAction::DoWhile(cond, actions) => {
                let mut iters = 0;
                loop {
                    iters += 1;
                    if iters > self.max_loop_iterations {
                        break;
                    }
                    let mut do_break = false;
                    for action in actions {
                        match self.exec_action(action) {
                            AwkFlow::Continue => {}
                            AwkFlow::Break => {
                                do_break = true;
                                break;
                            }
                            AwkFlow::LoopContinue => break,
                            flow => return flow,
                        }
                    }
                    if do_break || !self.eval_expr(cond).as_bool() {
                        break;
                    }
                }
                AwkFlow::Continue
            }
            AwkAction::For(init, cond, update, actions) => {
                self.exec_action(init);
                let mut iters = 0;
                while self.eval_expr(cond).as_bool() {
                    iters += 1;
                    if iters > self.max_loop_iterations {
                        break;
                    }
                    let mut do_break = false;
                    for action in actions {
                        match self.exec_action(action) {
                            AwkFlow::Continue => {}
                            AwkFlow::Break => {
                                do_break = true;
                                break;
                            }
                            AwkFlow::LoopContinue => break,
                            flow => return flow,
                        }
                    }
                    if do_break {
                        break;
                    }
                    self.exec_action(update);
                }
                AwkFlow::Continue
            }
            AwkAction::ForIn(var, arr_name, actions) => {
                // Collect array keys matching the pattern arr_name[*]
                let prefix = format!("{}[", arr_name);
                let mut keys: Vec<String> = self
                    .state
                    .variables
                    .keys()
                    .filter(|k| k.starts_with(&prefix) && k.ends_with(']'))
                    .map(|k| k[prefix.len()..k.len() - 1].to_string())
                    .collect();
                // Sort for deterministic iteration: numeric keys first, then lexical
                keys.sort_by(|a, b| match (a.parse::<f64>(), b.parse::<f64>()) {
                    (Ok(na), Ok(nb)) => na.partial_cmp(&nb).unwrap_or(std::cmp::Ordering::Equal),
                    _ => a.cmp(b),
                });

                let mut iters = 0;
                for key in keys {
                    iters += 1;
                    if iters > self.max_loop_iterations {
                        break;
                    }
                    self.state.set_variable(var, AwkValue::String(key));
                    let mut do_break = false;
                    for action in actions {
                        match self.exec_action(action) {
                            AwkFlow::Continue => {}
                            AwkFlow::Break => {
                                do_break = true;
                                break;
                            }
                            AwkFlow::LoopContinue => break,
                            flow => return flow,
                        }
                    }
                    if do_break {
                        break;
                    }
                }
                AwkFlow::Continue
            }
            AwkAction::Delete(arr_name, key) => {
                let k = self.eval_expr(key).as_string();
                if k == "*" {
                    // Delete all entries in the array
                    let prefix = format!("{}[", arr_name);
                    let keys: Vec<String> = self
                        .state
                        .variables
                        .keys()
                        .filter(|k| k.starts_with(&prefix))
                        .cloned()
                        .collect();
                    for key in keys {
                        self.state.variables.remove(&key);
                    }
                } else {
                    let full_key = format!("{}[{}]", arr_name, k);
                    self.state.variables.remove(&full_key);
                }
                AwkFlow::Continue
            }
            AwkAction::Next => AwkFlow::Next,
            AwkAction::Getline => {
                // Advance to next input line and update $0, NR, NF, FNR
                self.line_index += 1;
                if self.line_index < self.input_lines.len() {
                    let line = self.input_lines[self.line_index].clone();
                    self.state.set_line(&line);
                }
                AwkFlow::Continue
            }
            AwkAction::GetlineFile { var, file } => {
                // Read next line from file (action context — return value discarded).
                let path_str = self.eval_expr(file).as_string();
                let resolved = if path_str.starts_with('/') {
                    path_str.clone()
                } else {
                    self.cwd.join(&path_str).to_string_lossy().to_string()
                };

                if !self.ensure_file_loaded(&resolved) {
                    return AwkFlow::Continue;
                }

                let entry = self.file_inputs.get_mut(&resolved).unwrap();
                if entry.1 < entry.0.len() {
                    let line = entry.0[entry.1].clone();
                    entry.1 += 1;
                    match var {
                        Some(v) => {
                            self.state
                                .variables
                                .insert(v.clone(), AwkValue::String(line));
                        }
                        None => {
                            self.state.set_line(&line);
                        }
                    }
                }
                AwkFlow::Continue
            }
            AwkAction::Break => AwkFlow::Break,
            AwkAction::Continue => AwkFlow::LoopContinue,
            AwkAction::Exit(expr) => {
                let code = expr.as_ref().map(|e| self.eval_expr(e).as_number() as i32);
                AwkFlow::Exit(code)
            }
            AwkAction::Return(expr) => {
                let val = expr
                    .as_ref()
                    .map(|e| self.eval_expr(e))
                    .unwrap_or(AwkValue::Uninitialized);
                AwkFlow::Return(val)
            }
            AwkAction::Expression(expr) => {
                self.eval_expr(expr);
                AwkFlow::Continue
            }
        }
    }

    fn matches_pattern(&mut self, pattern: &AwkPattern) -> bool {
        match pattern {
            AwkPattern::Regex(re) => {
                let line = self.state.get_field(0).as_string();
                re.is_match(&line)
            }
            AwkPattern::Expression(expr) => self.eval_expr(expr).as_bool(),
            // Range patterns are handled specially via matches_pattern_with_index
            // which tracks state. This arm shouldn't normally be reached for ranges
            // in the main loop, but handle it defensively.
            AwkPattern::Range(_, _) => false,
        }
    }

    /// Check if a rule's pattern matches, with range state tracking by rule index.
    fn matches_pattern_with_index(&mut self, pattern: &AwkPattern, rule_idx: usize) -> bool {
        match pattern {
            AwkPattern::Range(start, end) => {
                let active = *self.range_active.get(&rule_idx).unwrap_or(&false);
                if active {
                    // Already in range — check if end pattern matches
                    if self.matches_pattern(end) {
                        // End pattern matched: include this line, deactivate range
                        self.range_active.insert(rule_idx, false);
                    }
                    true
                } else {
                    // Not in range — check if start pattern matches
                    if self.matches_pattern(start) {
                        // If end also matches this same line, range closes immediately.
                        let end_matches = self.matches_pattern(end);
                        self.range_active.insert(rule_idx, !end_matches);
                        true
                    } else {
                        false
                    }
                }
            }
            other => self.matches_pattern(other),
        }
    }
}

impl Awk {
    /// Process C-style escape sequences in a string (e.g., \t → tab, \n → newline)
    fn process_escape_sequences(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('t') => result.push('\t'),
                    Some('n') => result.push('\n'),
                    Some('r') => result.push('\r'),
                    Some('\\') => result.push('\\'),
                    Some('a') => result.push('\x07'),
                    Some('b') => result.push('\x08'),
                    Some('f') => result.push('\x0C'),
                    Some(other) => {
                        result.push('\\');
                        result.push(other);
                    }
                    None => result.push('\\'),
                }
            } else {
                result.push(c);
            }
        }
        result
    }
}

#[async_trait]
impl Builtin for Awk {
    async fn execute(&self, ctx: Context<'_>) -> Result<ExecResult> {
        if let Some(r) = super::check_help_version(
            ctx.args,
            "Usage: awk [OPTION]... 'program' [FILE]...\nPattern scanning and processing language.\n\n  -F SEP\t\tuse SEP as field separator\n  -v var=val\tassign variable before execution\n  -f progfile\tread program from file\n  --csv, -k\tCSV mode (set field separator to comma)\n  --help\tdisplay this help and exit\n  --version\toutput version information and exit\n",
            Some("awk (bashkit) 0.1"),
        ) {
            return Ok(r);
        }
        let mut program_str = String::new();
        let mut files: Vec<String> = Vec::new();
        let mut field_sep = " ".to_string();
        let mut pre_vars: Vec<(String, String)> = Vec::new();
        let mut csv_mode = false;
        let mut i = 0;

        while i < ctx.args.len() {
            let arg = &ctx.args[i];
            if arg == "--csv" || arg == "-k" {
                csv_mode = true;
                field_sep = ",".to_string();
            } else if arg == "-F" {
                i += 1;
                if i < ctx.args.len() {
                    field_sep = ctx.args[i].clone();
                }
            } else if let Some(sep) = arg.strip_prefix("-F") {
                field_sep = sep.to_string();
            } else if arg == "-v" {
                // Variable assignment: -v var=value
                i += 1;
                if i < ctx.args.len()
                    && let Some(eq_pos) = ctx.args[i].find('=')
                {
                    let name = ctx.args[i][..eq_pos].to_string();
                    let mut value = ctx.args[i][eq_pos + 1..].to_string();
                    // Strip surrounding quotes if present (shell may pass them)
                    if (value.starts_with('"') && value.ends_with('"'))
                        || (value.starts_with('\'') && value.ends_with('\''))
                    {
                        value = value[1..value.len() - 1].to_string();
                    }
                    pre_vars.push((name, value));
                }
            } else if arg == "-f" {
                // Read program from file
                i += 1;
                if i < ctx.args.len() {
                    let path = if ctx.args[i].starts_with('/') {
                        std::path::PathBuf::from(&ctx.args[i])
                    } else {
                        ctx.cwd.join(&ctx.args[i])
                    };
                    program_str = match read_text_file(&*ctx.fs, &path, "awk").await {
                        Ok(t) => t,
                        Err(e) => return Ok(e),
                    };
                }
            } else if arg.starts_with('-') {
                // Unknown option - ignore
            } else if program_str.is_empty() {
                program_str = arg.clone();
            } else {
                files.push(arg.clone());
            }
            i += 1;
        }

        if program_str.is_empty() {
            return Err(Error::Execution("awk: no program given".to_string()));
        }

        let program_str = normalize_awk_newlines(&program_str);
        let mut parser = AwkParser::new(&program_str);
        let program = parser.parse()?;

        let mut interp = AwkInterpreter::new();
        interp.max_loop_iterations = ctx
            .execution_extension::<ExecutionLimits>()
            .map(|limits| limits.max_loop_iterations)
            .unwrap_or_else(|| ExecutionLimits::default().max_loop_iterations);
        interp.functions = program.functions.clone();
        interp.state.fs = Self::process_escape_sequences(&field_sep);
        interp.fs = Some(ctx.fs.clone());
        interp.cwd = ctx.cwd.clone();
        if csv_mode {
            interp.state.csv_mode = true;
            interp.state.ofs = ",".to_string();
        }

        // Set pre-assigned variables (-v)
        for (name, value) in &pre_vars {
            let awk_val = if let Ok(n) = value.parse::<f64>() {
                AwkValue::Number(n)
            } else {
                AwkValue::String(value.clone())
            };
            interp.state.set_variable(name, awk_val);
        }

        // Run BEGIN actions
        let mut exit_code: Option<i32> = None;
        for action in &program.begin_actions {
            if let AwkFlow::Exit(code) = interp.exec_action(action) {
                exit_code = code;
                // Run END actions even after exit
                for end_action in &program.end_actions {
                    if let AwkFlow::Exit(_) = interp.exec_action(end_action) {
                        break;
                    }
                }
                Self::flush_file_outputs(&interp, &ctx).await?;
                let mut result = ExecResult::with_code(interp.output, exit_code.unwrap_or(0));
                result.stderr = interp.stderr_output;
                return Ok(result);
            }
        }

        // Process input
        let inputs: Vec<String> = if files.is_empty() {
            vec![ctx.stdin.unwrap_or("").to_string()]
        } else {
            let mut inputs = Vec::new();
            for file in &files {
                let path = if file.starts_with('/') {
                    std::path::PathBuf::from(file)
                } else {
                    ctx.cwd.join(file)
                };

                let text = match read_text_file(&*ctx.fs, &path, "awk").await {
                    Ok(t) => t,
                    Err(e) => return Ok(e),
                };
                inputs.push(text);
            }
            inputs
        };

        'files: for (file_idx, input) in inputs.iter().enumerate() {
            interp.state.fnr = 0;
            // Set FILENAME to current file path, or empty for stdin
            if !files.is_empty() {
                interp.state.variables.insert(
                    "FILENAME".to_string(),
                    AwkValue::String(files[file_idx].clone()),
                );
            } else {
                interp
                    .state
                    .variables
                    .insert("FILENAME".to_string(), AwkValue::String(String::new()));
            }
            // Index-based iteration so getline can advance the index
            interp.input_lines = input.lines().map(|l| l.to_string()).collect();
            interp.line_index = 0;

            while interp.line_index < interp.input_lines.len() {
                let line = interp.input_lines[interp.line_index].clone();
                interp.state.set_line(&line);

                for (rule_idx, rule) in program.main_rules.iter().enumerate() {
                    // Check pattern (with range state tracking)
                    let matches = match &rule.pattern {
                        Some(pattern) => interp.matches_pattern_with_index(pattern, rule_idx),
                        None => true,
                    };

                    if matches {
                        let mut next_record = false;
                        for action in &rule.actions {
                            match interp.exec_action(action) {
                                AwkFlow::Continue => {}
                                AwkFlow::Next => {
                                    next_record = true;
                                    break;
                                }
                                AwkFlow::Exit(code) => {
                                    exit_code = code;
                                    break 'files;
                                }
                                _ => {}
                            }
                        }
                        if next_record {
                            break;
                        }
                    }
                }
                interp.line_index += 1;
            }
        }

        // Run END actions (awk runs END even after exit in main body)
        for action in &program.end_actions {
            if let AwkFlow::Exit(code) = interp.exec_action(action) {
                if exit_code.is_none() {
                    exit_code = code;
                }
                break;
            }
        }

        Self::flush_file_outputs(&interp, &ctx).await?;
        let mut result = ExecResult::with_code(interp.output, exit_code.unwrap_or(0));
        result.stderr = interp.stderr_output;
        Ok(result)
    }
}

impl Awk {
    /// Flush buffered file outputs to VFS.
    async fn flush_file_outputs(interp: &AwkInterpreter, ctx: &Context<'_>) -> Result<()> {
        // Truncate mode: write entire buffer as file content
        for (path, content) in &interp.file_outputs {
            let path = if path.starts_with('/') {
                std::path::PathBuf::from(path)
            } else {
                ctx.cwd.join(path)
            };
            ctx.fs.write_file(&path, content.as_bytes()).await?;
        }
        // Append mode: append buffer to existing file (or create)
        for (path, content) in &interp.file_appends {
            let path = if path.starts_with('/') {
                std::path::PathBuf::from(path)
            } else {
                ctx.cwd.join(path)
            };
            ctx.fs.append_file(&path, content.as_bytes()).await?;
        }
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::fs::{FileSystem, InMemoryFs};
    use std::collections::HashMap;
    use std::path::PathBuf;
    use std::sync::Arc;

    async fn run_awk(args: &[&str], stdin: Option<&str>) -> Result<ExecResult> {
        let awk = Awk;
        let fs = Arc::new(InMemoryFs::new());
        let mut vars = HashMap::new();
        let mut cwd = PathBuf::from("/");
        let args: Vec<String> = args.iter().map(|s| s.to_string()).collect();

        let ctx = Context {
            args: &args,
            env: &HashMap::new(),
            variables: &mut vars,
            cwd: &mut cwd,
            fs,
            stdin,
            #[cfg(feature = "http_client")]
            http_client: None,
            #[cfg(feature = "git")]
            git_client: None,
            #[cfg(feature = "ssh")]
            ssh_client: None,
            shell: None,
        };

        awk.execute(ctx).await
    }

    #[tokio::test]
    async fn test_awk_print_all() {
        let result = run_awk(&["{print}"], Some("hello\nworld")).await.unwrap();
        assert_eq!(result.stdout, "hello\nworld\n");
    }

    #[tokio::test]
    async fn test_awk_print_field() {
        let result = run_awk(&["{print $1}"], Some("hello world\nfoo bar"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "hello\nfoo\n");
    }

    #[tokio::test]
    async fn test_awk_print_multiple_fields() {
        let result = run_awk(&["{print $2, $1}"], Some("hello world"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "world hello\n");
    }

    #[tokio::test]
    async fn test_awk_field_separator() {
        let result = run_awk(&["-F:", "{print $1}"], Some("root:x:0:0"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "root\n");
    }

    #[tokio::test]
    async fn test_awk_nr() {
        let result = run_awk(&["{print NR, $0}"], Some("a\nb\nc")).await.unwrap();
        assert_eq!(result.stdout, "1 a\n2 b\n3 c\n");
    }

    #[tokio::test]
    async fn test_awk_nf() {
        let result = run_awk(&["{print NF}"], Some("a b c\nd e")).await.unwrap();
        assert_eq!(result.stdout, "3\n2\n");
    }

    #[tokio::test]
    async fn test_awk_begin_end() {
        let result = run_awk(
            &["BEGIN{print \"start\"} {print} END{print \"end\"}"],
            Some("middle"),
        )
        .await
        .unwrap();
        assert_eq!(result.stdout, "start\nmiddle\nend\n");
    }

    #[tokio::test]
    async fn test_awk_pattern() {
        let result = run_awk(&["/hello/{print}"], Some("hello\nworld\nhello again"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "hello\nhello again\n");
    }

    #[tokio::test]
    async fn test_awk_condition() {
        let result = run_awk(&["NR==2{print}"], Some("line1\nline2\nline3"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "line2\n");
    }

    #[tokio::test]
    async fn test_awk_arithmetic() {
        let result = run_awk(&["{print $1 + $2}"], Some("1 2\n3 4"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "3\n7\n");
    }

    #[tokio::test]
    async fn test_awk_variables() {
        let result = run_awk(&["{sum += $1} END{print sum}"], Some("1\n2\n3\n4"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "10\n");
    }

    #[tokio::test]
    async fn test_awk_unicode_identifier_no_panic() {
        let result = run_awk(&["BEGIN{café=7; print café}"], None).await.unwrap();
        assert_eq!(result.stdout, "7\n");
    }

    #[tokio::test]
    async fn test_awk_length() {
        let result = run_awk(&["{print length($0)}"], Some("hello\nhi"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "5\n2\n");
    }

    #[tokio::test]
    async fn test_awk_substr() {
        let result = run_awk(&["{print substr($0, 2, 3)}"], Some("hello"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "ell\n");
    }

    #[tokio::test]
    async fn test_awk_toupper() {
        let result = run_awk(&["{print toupper($0)}"], Some("hello"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "HELLO\n");
    }

    #[tokio::test]
    async fn test_awk_multi_statement() {
        // Test multiple statements separated by semicolon
        let result = run_awk(&["{x=1; print x}"], Some("test")).await.unwrap();
        assert_eq!(result.stdout, "1\n");
    }

    #[tokio::test]
    async fn test_awk_gsub_with_print() {
        // gsub with regex literal followed by print
        let result = run_awk(
            &[r#"{gsub(/hello/, "hi"); print}"#],
            Some("hello hello hello"),
        )
        .await
        .unwrap();
        assert_eq!(result.stdout, "hi hi hi\n");
    }

    #[tokio::test]
    async fn test_awk_split_with_array_access() {
        // split with array indexing
        let result = run_awk(
            &[r#"{n = split($0, arr, ":"); print arr[2]}"#],
            Some("a:b:c"),
        )
        .await
        .unwrap();
        assert_eq!(result.stdout, "b\n");
    }

    /// TM-DOS-027: Deeply nested parenthesized expressions must be rejected
    #[test]
    fn test_awk_parser_depth_limit_parens() {
        // Build expression with 150 nested parens: (((((...(1)...))))
        let depth = 150;
        let open = "(".repeat(depth);
        let close = ")".repeat(depth);
        let program = format!("{{print {open}1{close}}}");

        let mut parser = AwkParser::new(&program);
        let result = parser.parse();
        assert!(result.is_err(), "deeply nested parens must be rejected");
        let err = result.unwrap_err().to_string();
        assert!(
            err.contains("nesting too deep"),
            "error should mention nesting: {err}"
        );
    }

    /// TM-DOS-027: Deeply chained unary operators must be rejected
    #[test]
    fn test_awk_parser_depth_limit_unary() {
        // Build expression with 200 chained negations: - - - ... - 1
        let depth = 200;
        let prefix = "- ".repeat(depth);
        let program = format!("{{print {prefix}1}}");

        let mut parser = AwkParser::new(&program);
        let result = parser.parse();
        assert!(result.is_err(), "deeply chained unary ops must be rejected");
        let err = result.unwrap_err().to_string();
        assert!(
            err.contains("nesting too deep"),
            "error should mention nesting: {err}"
        );
    }

    /// TM-DOS-027: Moderate nesting within limit still works
    #[test]
    fn test_awk_parser_moderate_nesting_ok() {
        // 10 levels of parens should be fine
        let depth = 10;
        let open = "(".repeat(depth);
        let close = ")".repeat(depth);
        let program = format!("{{print {open}1{close}}}");

        let mut parser = AwkParser::new(&program);
        let result = parser.parse();
        assert!(
            result.is_ok(),
            "moderate nesting should succeed: {:?}", // debug-ok: assert-failure message
            result.err()
        );
    }

    // === New tests for added features ===

    #[tokio::test]
    async fn test_awk_for_c_style() {
        let result = run_awk(&["BEGIN{for(i=1;i<=5;i++) print i}"], None)
            .await
            .unwrap();
        assert_eq!(result.stdout, "1\n2\n3\n4\n5\n");
    }

    #[tokio::test]
    async fn test_awk_for_with_body_block() {
        let result = run_awk(&["BEGIN{for(i=0;i<3;i++){print i}}"], None)
            .await
            .unwrap();
        assert_eq!(result.stdout, "0\n1\n2\n");
    }

    #[tokio::test]
    async fn test_awk_while_loop() {
        let result = run_awk(&["BEGIN{i=1; while(i<=3){print i; i++}}"], None)
            .await
            .unwrap();
        assert_eq!(result.stdout, "1\n2\n3\n");
    }

    #[tokio::test]
    async fn test_awk_do_while() {
        let result = run_awk(&["BEGIN{i=1; do{print i; i++}while(i<=3)}"], None)
            .await
            .unwrap();
        assert_eq!(result.stdout, "1\n2\n3\n");
    }

    #[tokio::test]
    async fn test_awk_post_increment() {
        let result = run_awk(&["{print i++}"], Some("a\nb\nc")).await.unwrap();
        assert_eq!(result.stdout, "0\n1\n2\n");
    }

    #[tokio::test]
    async fn test_awk_pre_increment() {
        let result = run_awk(&["{print ++i}"], Some("a\nb\nc")).await.unwrap();
        assert_eq!(result.stdout, "1\n2\n3\n");
    }

    #[tokio::test]
    async fn test_awk_post_decrement() {
        let result = run_awk(&["BEGIN{x=3; print x--; print x}"], None)
            .await
            .unwrap();
        assert_eq!(result.stdout, "3\n2\n");
    }

    #[tokio::test]
    async fn test_awk_array_assign() {
        let result = run_awk(&[r#"BEGIN{a[1]="x"; a[2]="y"; print a[1], a[2]}"#], None)
            .await
            .unwrap();
        assert_eq!(result.stdout, "x y\n");
    }

    #[tokio::test]
    async fn test_awk_array_in_operator() {
        let result = run_awk(
            &[r#"BEGIN{a["foo"]=1; if("foo" in a) print "yes"; if("bar" in a) print "no"}"#],
            None,
        )
        .await
        .unwrap();
        assert_eq!(result.stdout, "yes\n");
    }

    #[tokio::test]
    async fn test_awk_for_in_loop() {
        let result = run_awk(
            &[r#"BEGIN{a[1]="x"; a[2]="y"; for(k in a) count++; print count}"#],
            None,
        )
        .await
        .unwrap();
        assert_eq!(result.stdout, "2\n");
    }

    #[tokio::test]
    async fn test_awk_delete_array_element() {
        let result = run_awk(
            &[r#"BEGIN{a[1]=1; a[2]=2; delete a[1]; for(k in a) print k, a[k]}"#],
            None,
        )
        .await
        .unwrap();
        assert_eq!(result.stdout, "2 2\n");
    }

    #[tokio::test]
    async fn test_awk_v_flag() {
        let result = run_awk(&["-v", "x=hello", "BEGIN{print x}"], None)
            .await
            .unwrap();
        assert_eq!(result.stdout, "hello\n");
    }

    #[tokio::test]
    async fn test_awk_v_flag_numeric() {
        let result = run_awk(&["-v", "n=42", "BEGIN{print n+1}"], None)
            .await
            .unwrap();
        assert_eq!(result.stdout, "43\n");
    }

    #[tokio::test]
    async fn test_awk_break_in_for() {
        let result = run_awk(&["BEGIN{for(i=1;i<=10;i++){if(i>3) break; print i}}"], None)
            .await
            .unwrap();
        assert_eq!(result.stdout, "1\n2\n3\n");
    }

    #[tokio::test]
    async fn test_awk_continue_in_for() {
        let result = run_awk(
            &["BEGIN{for(i=1;i<=5;i++){if(i==3) continue; print i}}"],
            None,
        )
        .await
        .unwrap();
        assert_eq!(result.stdout, "1\n2\n4\n5\n");
    }

    #[tokio::test]
    async fn test_awk_ternary() {
        let result = run_awk(&[r#"{print ($1>2 ? "big" : "small")}"#], Some("1\n3\n2"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "small\nbig\nsmall\n");
    }

    #[tokio::test]
    async fn test_awk_field_assignment() {
        let result = run_awk(&[r#"{$2="new"; print}"#], Some("one two three"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "one new three\n");
    }

    #[tokio::test]
    async fn test_awk_csv_to_json_pattern() {
        // This is the pattern LLMs use for CSV→JSON conversion
        let result = run_awk(
            &[
                "-F,",
                r#"NR==1{for(i=1;i<=NF;i++) h[i]=$i; next} {for(i=1;i<=NF;i++) printf "%s=%s ", h[i], $i; print ""}"#,
            ],
            Some("name,age\nalice,30\nbob,25"),
        )
        .await
        .unwrap();
        assert!(result.stdout.contains("name=alice"));
        assert!(result.stdout.contains("age=30"));
        assert!(result.stdout.contains("name=bob"));
    }

    #[tokio::test]
    async fn test_awk_compound_array_assign() {
        let result = run_awk(
            &[r#"{count[$1]++} END{for(k in count) print k, count[k]}"#],
            Some("a\nb\na\nc\nb\na"),
        )
        .await
        .unwrap();
        // Order may vary, so check contents
        assert!(result.stdout.contains("a 3"));
        assert!(result.stdout.contains("b 2"));
        assert!(result.stdout.contains("c 1"));
    }

    #[tokio::test]
    async fn test_awk_next_statement() {
        let result = run_awk(&["NR==2{next} {print}"], Some("line1\nline2\nline3"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "line1\nline3\n");
    }

    #[tokio::test]
    async fn test_awk_exit_statement() {
        let result = run_awk(&["NR==2{exit} {print}"], Some("line1\nline2\nline3"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "line1\n");
    }

    #[tokio::test]
    async fn test_awk_getline_basic() {
        let result = run_awk(&["{getline; print}"], Some("line1\nline2"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "line2\n");
    }

    #[tokio::test]
    async fn test_awk_getline_updates_fields() {
        let result = run_awk(&["{getline; print $1}"], Some("a b\nc d"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "c\n");
    }

    #[tokio::test]
    async fn test_awk_getline_at_eof() {
        // getline at EOF should keep current $0
        let result = run_awk(&["{getline; print}"], Some("only")).await.unwrap();
        assert_eq!(result.stdout, "only\n");
    }

    #[tokio::test]
    async fn test_awk_revenue_calculation() {
        // This is the exact eval task pattern
        let result = run_awk(
            &["-F,", "NR>1{total+=$2*$3} END{print total}"],
            Some("product,price,quantity\nwidget,10,5\ngadget,25,3\ndoohickey,7,12\nsprocket,15,8"),
        )
        .await
        .unwrap();
        assert_eq!(result.stdout, "329\n");
    }

    #[tokio::test]
    async fn test_awk_printf_parens() {
        // printf with parenthesized syntax: printf("format", args)
        let result = run_awk(
            &[r#"BEGIN{printf("["); printf("%s", "x"); printf("]"); print ""}"#],
            Some(""),
        )
        .await
        .unwrap();
        assert_eq!(result.stdout, "[x]\n");
    }

    #[tokio::test]
    async fn test_awk_printf_parens_csv() {
        // CSV to JSON pattern using printf with parens
        let result = run_awk(
            &[
                "-F,",
                r#"NR==1{for(i=1;i<=NF;i++) h[i]=$i; next} {printf("%s{", (NR>2?",":"")); for(i=1;i<=NF;i++){printf("%s\"%s\":\"%s\"", (i>1?",":""), h[i], $i)}; printf("}")} END{print ""}"#,
            ],
            Some("name,age\nalice,30\nbob,25\n"),
        )
        .await
        .unwrap();
        assert!(result.stdout.contains("alice"));
        assert!(result.stdout.contains("bob"));
    }

    #[tokio::test]
    async fn test_awk_recursive_function_depth_limit() {
        // Recursive function should be limited, not stack overflow
        let result = run_awk(
            &[r#"function r(n) { return r(n+1) } BEGIN { print r(0) }"#],
            Some(""),
        )
        .await
        .unwrap();
        // Should complete without crashing (returns Uninitialized -> empty string)
        assert_eq!(result.exit_code, 0);
    }

    #[tokio::test]
    async fn test_awk_while_loop_limited() {
        // Infinite while loop should terminate via default max_loop_iterations
        let result = run_awk(
            &[r#"BEGIN { i=0; while(1) { i++; if(i>200000) break } print i }"#],
            Some(""),
        )
        .await
        .unwrap();
        assert_eq!(result.exit_code, 0);
        let count: usize = result.stdout.trim().parse().unwrap();
        // Should be capped at default max_loop_iterations (10_000), not 200_000
        assert!(
            count <= 10_001,
            "loop ran {} times, expected <= 10001",
            count
        );
    }

    #[tokio::test]
    async fn test_awk_unicode_in_comment() {
        // Issue #395: multi-byte Unicode chars in comments should not panic
        let result = run_awk(&["# ── header ──────\n{ print $1 }"], Some("hello world\n"))
            .await
            .unwrap();
        assert_eq!(result.exit_code, 0);
        assert_eq!(result.stdout.trim(), "hello");
    }

    #[tokio::test]
    async fn test_awk_unicode_in_string() {
        // Multi-byte chars in string literals should not panic
        let result = run_awk(&[r#"BEGIN { print "café" }"#], Some(""))
            .await
            .unwrap();
        assert_eq!(result.exit_code, 0);
        assert_eq!(result.stdout.trim(), "café");
    }

    #[tokio::test]
    async fn test_awk_array_assign_field_ref_subscript() {
        // Issue #396.1: arr[$1] = $3 should work with field refs as subscripts
        let result = run_awk(
            &["{ arr[$1] = $2 } END { print arr[\"hello\"] }"],
            Some("hello world\n"),
        )
        .await
        .unwrap();
        assert_eq!(result.exit_code, 0);
        assert_eq!(result.stdout.trim(), "world");
    }

    #[tokio::test]
    async fn test_awk_multi_subscript() {
        // Issue #396.2: a["x","y"] multi-subscript with SUBSEP
        let result = run_awk(&[r#"BEGIN { a["x","y"] = 1; print a["x","y"] }"#], Some(""))
            .await
            .unwrap();
        assert_eq!(result.exit_code, 0);
        assert_eq!(result.stdout.trim(), "1");
    }

    #[tokio::test]
    async fn test_awk_subsep_defined() {
        // Issue #396.3: SUBSEP should be defined as \034
        let result = run_awk(&[r#"BEGIN { print length(SUBSEP) }"#], Some(""))
            .await
            .unwrap();
        assert_eq!(result.exit_code, 0);
        assert_eq!(result.stdout.trim(), "1");
    }

    #[tokio::test]
    async fn test_awk_preincrement_array() {
        // Issue #396.4: ++arr[key] should work
        let result = run_awk(
            &["{ ++count[$1] } END { for (k in count) print k, count[k] }"],
            Some("a\nb\na\n"),
        )
        .await
        .unwrap();
        assert_eq!(result.exit_code, 0);
        assert!(result.stdout.contains("a 2"));
        assert!(result.stdout.contains("b 1"));
    }

    /// Helper that returns the VFS alongside the result for testing file output.
    async fn run_awk_with_fs(
        args: &[&str],
        stdin: Option<&str>,
    ) -> (Result<ExecResult>, Arc<InMemoryFs>) {
        let awk = Awk;
        let fs = Arc::new(InMemoryFs::new());
        let mut vars = HashMap::new();
        let mut cwd = PathBuf::from("/");
        let args: Vec<String> = args.iter().map(|s| s.to_string()).collect();

        let ctx = Context {
            args: &args,
            env: &HashMap::new(),
            variables: &mut vars,
            cwd: &mut cwd,
            fs: fs.clone(),
            stdin,
            #[cfg(feature = "http_client")]
            http_client: None,
            #[cfg(feature = "git")]
            git_client: None,
            #[cfg(feature = "ssh")]
            ssh_client: None,
            shell: None,
        };

        let result = awk.execute(ctx).await;
        (result, fs)
    }

    #[tokio::test]
    async fn test_awk_print_redirect_truncate() {
        // Issue #607: print ... > file should create file with content
        let (result, fs) = run_awk_with_fs(&[r#"BEGIN{print "hello" > "/tmp/out"}"#], None).await;
        let result = result.unwrap();
        assert_eq!(result.exit_code, 0);
        // stdout should be empty (output went to file)
        assert_eq!(result.stdout, "");
        let content = fs
            .read_file(std::path::Path::new("/tmp/out"))
            .await
            .unwrap();
        assert_eq!(String::from_utf8_lossy(&content), "hello\n");
    }

    #[tokio::test]
    async fn test_awk_printf_redirect_truncate() {
        // Issue #607: printf ... > file should create file with content
        let (result, fs) = run_awk_with_fs(&[r#"BEGIN{printf "hello" > "/tmp/out"}"#], None).await;
        let result = result.unwrap();
        assert_eq!(result.exit_code, 0);
        assert_eq!(result.stdout, "");
        let content = fs
            .read_file(std::path::Path::new("/tmp/out"))
            .await
            .unwrap();
        assert_eq!(String::from_utf8_lossy(&content), "hello");
    }

    #[tokio::test]
    async fn test_awk_print_redirect_append() {
        // Issue #607: print ... >> file should append
        let (result, fs) = run_awk_with_fs(
            &[r#"BEGIN{print "a" > "/tmp/out"; print "b" >> "/tmp/out"}"#],
            None,
        )
        .await;
        let result = result.unwrap();
        assert_eq!(result.exit_code, 0);
        let content = fs
            .read_file(std::path::Path::new("/tmp/out"))
            .await
            .unwrap();
        assert_eq!(String::from_utf8_lossy(&content), "a\nb\n");
    }

    #[tokio::test]
    async fn test_awk_print_redirect_multiple_to_same_file() {
        // Multiple prints to same file with > should accumulate (AWK keeps file open)
        let (result, fs) = run_awk_with_fs(
            &[r#"BEGIN{print "line1" > "/tmp/out"; print "line2" > "/tmp/out"}"#],
            None,
        )
        .await;
        let result = result.unwrap();
        assert_eq!(result.exit_code, 0);
        let content = fs
            .read_file(std::path::Path::new("/tmp/out"))
            .await
            .unwrap();
        assert_eq!(String::from_utf8_lossy(&content), "line1\nline2\n");
    }

    #[tokio::test]
    async fn test_awk_print_redirect_pipe_unsupported() {
        // Pipe output should return clear error
        let (result, _fs) = run_awk_with_fs(&[r#"BEGIN{print "hello" | "cat"}"#], None).await;
        assert!(result.is_err());
        let err_msg = result.unwrap_err().to_string();
        assert!(
            err_msg.contains("pipe"),
            "expected pipe error, got: {err_msg}"
        );
    }

    // ========================================================================
    // --csv flag (issues #617, #618)
    // ========================================================================

    #[tokio::test]
    async fn test_awk_csv_basic() {
        let result = run_awk(&["--csv", "{print $2}"], Some("a,b,c\nd,e,f"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "b\ne\n");
    }

    #[tokio::test]
    async fn test_awk_csv_quoted_fields() {
        // Quoted field with embedded comma
        let result = run_awk(&["--csv", "{print $2}"], Some(r#"1,"hello, world",3"#))
            .await
            .unwrap();
        assert_eq!(result.stdout, "hello, world\n");
    }

    #[tokio::test]
    async fn test_awk_csv_escaped_quotes() {
        // Double-quote escaping per RFC 4180
        let result = run_awk(&["--csv", "{print $2}"], Some(r#"1,"she said ""hi""",3"#))
            .await
            .unwrap();
        assert_eq!(result.stdout, "she said \"hi\"\n");
    }

    #[tokio::test]
    async fn test_awk_csv_nf() {
        let result = run_awk(&["--csv", "{print NF}"], Some("a,b,c"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "3\n");
    }

    #[tokio::test]
    async fn test_awk_csv_ofs() {
        // In CSV mode, OFS defaults to comma
        let result = run_awk(&["--csv", "{print $1, $3}"], Some("a,b,c"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "a,c\n");
    }

    #[tokio::test]
    async fn test_awk_csv_empty_fields() {
        let result = run_awk(&["--csv", "{print NF}"], Some("a,,c,"))
            .await
            .unwrap();
        assert_eq!(result.stdout, "4\n");
    }

    #[tokio::test]
    async fn test_awk_csv_k_flag_alias() {
        // -k is an alias for --csv
        let result = run_awk(&["-k", "{print $2}"], Some("a,b,c")).await.unwrap();
        assert_eq!(result.stdout, "b\n");
    }

    #[test]
    fn test_csv_split_fields_unit() {
        assert_eq!(csv_split_fields("a,b,c"), vec!["a", "b", "c"]);
        assert_eq!(
            csv_split_fields(r#"1,"hello, world",3"#),
            vec!["1", "hello, world", "3"]
        );
        assert_eq!(csv_split_fields(r#""a""b",c"#), vec!["a\"b", "c"]);
        assert_eq!(csv_split_fields("a,,c,"), vec!["a", "", "c", ""]);
    }

    // ========================================================================
    // gawk 5.3+ Unicode escape sequences (issue #617)
    // ========================================================================

    #[tokio::test]
    async fn test_awk_unicode_escape_basic() {
        // \u followed by hex digits → Unicode character
        let result = run_awk(&[r#"BEGIN{print "\u0041"}"#], None).await.unwrap();
        assert_eq!(result.stdout, "A\n");
    }

    #[tokio::test]
    async fn test_awk_unicode_escape_multibyte() {
        // \u00E9 → é (Latin small e with acute)
        let result = run_awk(&[r#"BEGIN{print "\u00E9"}"#], None).await.unwrap();
        assert_eq!(result.stdout, "é\n");
    }

    #[tokio::test]
    async fn test_awk_unicode_escape_emoji() {
        // \u1F600 → 😀 (grinning face, 5 hex digits)
        let result = run_awk(&[r#"BEGIN{print "\u1F600"}"#], None).await.unwrap();
        assert_eq!(result.stdout, "😀\n");
    }

    #[tokio::test]
    async fn test_awk_unicode_escape_bare_u() {
        // \u with no hex digits → literal \u
        let result = run_awk(&[r#"BEGIN{print "\u "}"#], None).await.unwrap();
        assert_eq!(result.stdout, "\\u \n");
    }

    #[tokio::test]
    async fn test_awk_unicode_escape_mixed() {
        // Mix of Unicode escapes and regular text
        let result = run_awk(&[r#"BEGIN{print "caf\u00E9"}"#], None)
            .await
            .unwrap();
        assert_eq!(result.stdout, "café\n");
    }

    // ========================================================================
    // getline from file (issue #796)
    // ========================================================================

    /// Helper: create an in-memory FS with a file, then run awk.
    async fn run_awk_with_file(
        args: &[&str],
        stdin: Option<&str>,
        file_path: &str,
        file_content: &str,
    ) -> Result<ExecResult> {
        let awk = Awk;
        let fs = Arc::new(InMemoryFs::new());
        fs.write_file(std::path::Path::new(file_path), file_content.as_bytes())
            .await
            .unwrap();
        let mut vars = HashMap::new();
        let mut cwd = PathBuf::from("/");
        let args: Vec<String> = args.iter().map(|s| s.to_string()).collect();

        let ctx = Context {
            args: &args,
            env: &HashMap::new(),
            variables: &mut vars,
            cwd: &mut cwd,
            fs,
            stdin,
            #[cfg(feature = "http_client")]
            http_client: None,
            #[cfg(feature = "git")]
            git_client: None,
            #[cfg(feature = "ssh")]
            ssh_client: None,
            shell: None,
        };

        awk.execute(ctx).await
    }

    #[tokio::test]
    async fn test_awk_getline_file_into_var() {
        // getline var < "file" reads lines one at a time
        let result = run_awk_with_file(
            &[r#"BEGIN{while((getline line < "/tmp/data.txt") > 0) print line}"#],
            None,
            "/tmp/data.txt",
            "alpha\nbeta\ngamma",
        )
        .await
        .unwrap();
        assert_eq!(result.stdout, "alpha\nbeta\ngamma\n");
    }

    #[tokio::test]
    async fn test_awk_getline_file_no_var() {
        // getline < "file" without variable updates $0
        let result = run_awk_with_file(
            &[r#"BEGIN{getline < "/tmp/data.txt"; print}"#],
            None,
            "/tmp/data.txt",
            "first\nsecond",
        )
        .await
        .unwrap();
        assert_eq!(result.stdout, "first\n");
    }

    #[tokio::test]
    async fn test_awk_getline_file_eof() {
        // getline at EOF keeps variable unchanged
        let result = run_awk_with_file(
            &[r#"BEGIN{getline x < "/tmp/f.txt"; getline x < "/tmp/f.txt"; print x}"#],
            None,
            "/tmp/f.txt",
            "only",
        )
        .await
        .unwrap();
        assert_eq!(result.stdout, "only\n");
    }

    #[tokio::test]
    async fn test_awk_getline_file_missing() {
        // getline from non-existent file should not crash (returns -1 / empty)
        let result = run_awk(&[r#"BEGIN{getline x < "/no/such/file"; print "ok"}"#], None)
            .await
            .unwrap();
        assert_eq!(result.stdout, "ok\n");
    }

    #[tokio::test]
    async fn test_awk_output_limit_exceeded() {
        // Each iteration prints a 1000-char line. 100k iters = ~100MB >> 10MB limit.
        let result = run_awk(
            &[r#"BEGIN { s = sprintf("%1000s", "x"); for(i=0;i<100000;i++) print s }"#],
            None,
        )
        .await
        .unwrap();
        assert_eq!(result.exit_code, 2);
        assert!(
            result.stderr.contains("output limit exceeded"),
            "stderr should mention output limit: {}",
            result.stderr
        );
        assert!(
            result.stdout.len() <= 11_000_000,
            "stdout should be bounded: {} bytes",
            result.stdout.len()
        );
    }

    #[tokio::test]
    async fn test_awk_output_under_limit_ok() {
        // Small output well under 10MB should succeed normally
        let result = run_awk(&[r#"BEGIN { for(i=0;i<100;i++) print "hello" }"#], None)
            .await
            .unwrap();
        assert_eq!(result.exit_code, 0);
        let lines: Vec<&str> = result.stdout.trim().split('\n').collect();
        assert_eq!(lines.len(), 100);
    }

    #[tokio::test]
    async fn test_awk_file_redirect_output_limit() {
        // File redirect output should also be bounded
        let result = run_awk(
            &[r#"BEGIN { s = sprintf("%1000s", "x"); for(i=0;i<100000;i++) print s > "/tmp/out" }"#],
            None,
        )
        .await
        .unwrap();
        assert_eq!(result.exit_code, 2);
        assert!(
            result.stderr.contains("output limit exceeded"),
            "stderr should mention output limit: {}",
            result.stderr
        );
    }

    /// Helper: run AWK with a caller-provided VFS.
    async fn run_awk_with_custom_fs(
        args: &[&str],
        stdin: Option<&str>,
        fs: Arc<InMemoryFs>,
    ) -> Result<ExecResult> {
        let awk = Awk;
        let mut vars = HashMap::new();
        let mut cwd = PathBuf::from("/");
        let args: Vec<String> = args.iter().map(|s| s.to_string()).collect();

        let ctx = Context {
            args: &args,
            env: &HashMap::new(),
            variables: &mut vars,
            cwd: &mut cwd,
            fs,
            stdin,
            #[cfg(feature = "http_client")]
            http_client: None,
            #[cfg(feature = "git")]
            git_client: None,
            #[cfg(feature = "ssh")]
            ssh_client: None,
            shell: None,
        };

        awk.execute(ctx).await
    }

    #[tokio::test]
    async fn test_awk_getline_file_cache_limit_exceeded() {
        // Opening more than MAX_GETLINE_CACHED_FILES distinct files must fail
        // gracefully (getline returns -1 for new files beyond the limit).
        use crate::fs::FsLimits;

        let limits = FsLimits {
            max_file_count: 200_000,
            max_total_bytes: 200_000_000,
            ..FsLimits::default()
        };
        let fs = Arc::new(InMemoryFs::with_limits(limits));
        let count = MAX_GETLINE_CACHED_FILES + 5;
        for i in 0..count {
            fs.write_file(
                std::path::Path::new(&format!("/tmp/f{i}.txt")),
                format!("line{i}").as_bytes(),
            )
            .await
            .unwrap();
        }

        // AWK program: read one line from each file, count successes
        let prog = format!(
            r#"BEGIN{{ ok=0; for(i=0;i<{count};i++) {{ f="/tmp/f"i".txt"; if((getline x < f)>0) ok++ }} print ok }}"#,
        );
        let result = run_awk_with_custom_fs(&[&prog], None, fs).await.unwrap();
        let ok: usize = result.stdout.trim().parse().unwrap();
        // Exactly MAX_GETLINE_CACHED_FILES should succeed, rest should fail
        assert_eq!(ok, MAX_GETLINE_CACHED_FILES);
    }

    #[tokio::test]
    async fn test_awk_getline_file_cache_within_limit() {
        // Opening a reasonable number of files should all succeed.
        let fs = Arc::new(InMemoryFs::new());
        let count = 10;
        for i in 0..count {
            fs.write_file(
                std::path::Path::new(&format!("/tmp/f{i}.txt")),
                format!("data{i}").as_bytes(),
            )
            .await
            .unwrap();
        }

        let prog = format!(
            r#"BEGIN{{ ok=0; for(i=0;i<{count};i++) {{ f="/tmp/f"i".txt"; if((getline x < f)>0) ok++ }} print ok }}"#,
        );
        let result = run_awk_with_custom_fs(&[&prog], None, fs).await.unwrap();
        let ok: usize = result.stdout.trim().parse().unwrap();
        assert_eq!(ok, count);
    }

    #[tokio::test]
    async fn test_awk_getline_file_size_limit() {
        // A file exceeding FsLimits::max_file_size is rejected by getline.
        // Defense-in-depth: VFS also enforces limits, so a file at exactly
        // the boundary is accepted while one over is rejected at VFS level.
        use crate::fs::FsLimits;

        let limits = FsLimits {
            max_file_size: 100,
            ..FsLimits::unlimited()
        };
        let fs = Arc::new(InMemoryFs::with_limits(limits));
        // Write a file within limits -- should be readable via getline.
        fs.write_file(std::path::Path::new("/tmp/ok.txt"), &[b'a'; 100])
            .await
            .unwrap();
        // Attempt to write an oversized file -- VFS rejects it, so getline
        // returns -1 (file not found).
        let _ = fs
            .write_file(std::path::Path::new("/tmp/big.txt"), &[b'x'; 101])
            .await;

        // Within-limit file succeeds
        let result = run_awk_with_custom_fs(
            &[r#"BEGIN{r=(getline x < "/tmp/ok.txt"); print r}"#],
            None,
            fs.clone(),
        )
        .await
        .unwrap();
        assert_eq!(result.stdout, "1\n");

        // Over-limit file fails (not stored by VFS)
        let result = run_awk_with_custom_fs(
            &[r#"BEGIN{r=(getline x < "/tmp/big.txt"); print r}"#],
            None,
            fs,
        )
        .await
        .unwrap();
        assert_eq!(result.stdout, "-1\n");
    }

    // TM-INF-022: malformed-input corpus must not leak Debug shapes.
    const AWK_BANNED: &[&str] = &["AwkError::", "ParseError {", "Token::"];

    #[tokio::test]
    async fn no_leak_unbalanced_brace() {
        let r = crate::builtins::debug_leak_check::run("echo 1 | awk 'BEGIN { print'").await;
        crate::builtins::debug_leak_check::assert_no_leak(&r, "awk_unbalanced_brace", AWK_BANNED);
    }

    #[tokio::test]
    async fn no_leak_invalid_regex() {
        let r = crate::builtins::debug_leak_check::run("echo 1 | awk '/[/'").await;
        crate::builtins::debug_leak_check::assert_no_leak(&r, "awk_invalid_regex", AWK_BANNED);
    }

    #[tokio::test]
    async fn no_leak_undefined_function_call() {
        let r = crate::builtins::debug_leak_check::run(
            "echo 1 | awk 'BEGIN { totally_undefined_fn() }'",
        )
        .await;
        crate::builtins::debug_leak_check::assert_no_leak(
            &r,
            "awk_undefined_function_call",
            AWK_BANNED,
        );
    }
}
