use php_ast::ast::*;

pub(crate) fn is_declaration(kind: &StmtKind) -> bool {
    matches!(
        kind,
        StmtKind::Function(_)
            | StmtKind::Class(_)
            | StmtKind::Interface(_)
            | StmtKind::Trait(_)
            | StmtKind::Enum(_)
            | StmtKind::Namespace(_)
    )
}

pub(crate) fn needs_double_quotes(s: &str) -> bool {
    s.bytes().any(|b| {
        matches!(
            b,
            b'\n' | b'\r' | b'\t' | b'\x1b' | b'\x0c' | b'\x0b' | b'$'
        )
    })
}

pub(crate) fn escape_single_quoted(s: &str) -> String {
    let mut out = String::with_capacity(s.len());
    for ch in s.chars() {
        match ch {
            '\'' => out.push_str("\\'"),
            '\\' => out.push_str("\\\\"),
            _ => out.push(ch),
        }
    }
    out
}

/// Escape a literal segment of a heredoc body.
///
/// Heredocs preserve real newlines, and `"` needs no escape, but `\` and `$`
/// must still be escaped to prevent unintended escape sequences and variable
/// interpolation. Carriage returns must be escaped as `\r` to avoid parser
/// confusion with line endings.
pub(crate) fn escape_heredoc(s: &str) -> String {
    let mut out = String::with_capacity(s.len());
    for ch in s.chars() {
        match ch {
            '\\' => out.push_str("\\\\"),
            '$' => out.push_str("\\$"),
            '\r' => out.push_str("\\r"),
            _ => out.push(ch),
        }
    }
    out
}

pub(crate) fn escape_double_quoted(s: &str) -> String {
    let mut out = String::with_capacity(s.len());
    for ch in s.chars() {
        match ch {
            '"' => out.push_str("\\\""),
            '\\' => out.push_str("\\\\"),
            '$' => out.push_str("\\$"),
            '\n' => out.push_str("\\n"),
            '\r' => out.push_str("\\r"),
            '\t' => out.push_str("\\t"),
            '\x1b' => out.push_str("\\e"),
            '\x0c' => out.push_str("\\f"),
            '\x0b' => out.push_str("\\v"),
            _ => out.push(ch),
        }
    }
    out
}

/// Escape a literal segment of a backtick shell execution string.
///
/// Backticks handle escapes differently than double-quoted strings. Only `\\` and `\$`
/// are recognized escapes. To include a backtick in the literal, it must appear as `\``
/// where the backslash doesn't form a recognized escape, so both characters are treated
/// as literals by the parser.
pub(crate) fn escape_shell_exec(s: &str) -> String {
    let mut out = String::with_capacity(s.len());
    let mut chars = s.chars().peekable();

    while let Some(ch) = chars.next() {
        match ch {
            '\\' => {
                // Check if next char is backtick - if so, output as-is to create `\`` escape
                if chars.peek() == Some(&'`') {
                    out.push('\\');
                    out.push('`');
                    chars.next(); // consume the backtick
                } else {
                    // Otherwise escape the backslash normally
                    out.push_str("\\\\");
                }
            }
            '$' => {
                out.push_str("\\$");
            }
            '`' => {
                // Backtick on its own - must escape as `\``
                out.push('\\');
                out.push('`');
            }
            _ => {
                out.push(ch);
            }
        }
    }
    out
}

pub(crate) fn binary_op_str(op: BinaryOp) -> &'static str {
    match op {
        BinaryOp::Add => "+",
        BinaryOp::Sub => "-",
        BinaryOp::Mul => "*",
        BinaryOp::Div => "/",
        BinaryOp::Mod => "%",
        BinaryOp::Pow => "**",
        BinaryOp::Concat => ".",
        BinaryOp::Equal => "==",
        BinaryOp::NotEqual => "!=",
        BinaryOp::Identical => "===",
        BinaryOp::NotIdentical => "!==",
        BinaryOp::Less => "<",
        BinaryOp::Greater => ">",
        BinaryOp::LessOrEqual => "<=",
        BinaryOp::GreaterOrEqual => ">=",
        BinaryOp::Spaceship => "<=>",
        BinaryOp::BooleanAnd => "&&",
        BinaryOp::BooleanOr => "||",
        BinaryOp::BitwiseAnd => "&",
        BinaryOp::BitwiseOr => "|",
        BinaryOp::BitwiseXor => "^",
        BinaryOp::ShiftLeft => "<<",
        BinaryOp::ShiftRight => ">>",
        BinaryOp::LogicalAnd => "and",
        BinaryOp::LogicalOr => "or",
        BinaryOp::LogicalXor => "xor",
        BinaryOp::Instanceof => "instanceof",
        BinaryOp::Pipe => "|>",
    }
}

pub(crate) fn assign_op_str(op: AssignOp) -> &'static str {
    match op {
        AssignOp::Assign => "=",
        AssignOp::Plus => "+=",
        AssignOp::Minus => "-=",
        AssignOp::Mul => "*=",
        AssignOp::Div => "/=",
        AssignOp::Mod => "%=",
        AssignOp::Pow => "**=",
        AssignOp::Concat => ".=",
        AssignOp::BitwiseAnd => "&=",
        AssignOp::BitwiseOr => "|=",
        AssignOp::BitwiseXor => "^=",
        AssignOp::ShiftLeft => "<<=",
        AssignOp::ShiftRight => ">>=",
        AssignOp::Coalesce => "??=",
    }
}

pub(crate) fn unary_prefix_op_str(op: UnaryPrefixOp) -> &'static str {
    match op {
        UnaryPrefixOp::Negate => "-",
        UnaryPrefixOp::Plus => "+",
        UnaryPrefixOp::BooleanNot => "!",
        UnaryPrefixOp::BitwiseNot => "~",
        UnaryPrefixOp::PreIncrement => "++",
        UnaryPrefixOp::PreDecrement => "--",
    }
}

pub(crate) fn unary_postfix_op_str(op: UnaryPostfixOp) -> &'static str {
    match op {
        UnaryPostfixOp::PostIncrement => "++",
        UnaryPostfixOp::PostDecrement => "--",
    }
}

pub(crate) fn cast_str(kind: CastKind) -> &'static str {
    match kind {
        CastKind::Int => "(int)",
        CastKind::Float => "(float)",
        CastKind::String => "(string)",
        CastKind::Bool => "(bool)",
        CastKind::Array => "(array)",
        CastKind::Object => "(object)",
        CastKind::Unset => "(unset)",
        CastKind::Void => "(void)",
    }
}

pub(crate) fn include_kind_str(kind: IncludeKind) -> &'static str {
    match kind {
        IncludeKind::Include => "include",
        IncludeKind::IncludeOnce => "include_once",
        IncludeKind::Require => "require",
        IncludeKind::RequireOnce => "require_once",
    }
}

pub(crate) fn magic_const_str(kind: MagicConstKind) -> &'static str {
    match kind {
        MagicConstKind::Class => "__CLASS__",
        MagicConstKind::Dir => "__DIR__",
        MagicConstKind::File => "__FILE__",
        MagicConstKind::Function => "__FUNCTION__",
        MagicConstKind::Line => "__LINE__",
        MagicConstKind::Method => "__METHOD__",
        MagicConstKind::Namespace => "__NAMESPACE__",
        MagicConstKind::Trait => "__TRAIT__",
        MagicConstKind::Property => "__PROPERTY__",
    }
}

pub(crate) fn visibility_str(vis: Visibility) -> &'static str {
    match vis {
        Visibility::Public => "public",
        Visibility::Protected => "protected",
        Visibility::Private => "private",
    }
}
