use std::sync::Arc;

use crate::{ast::Expression, ir::DataSlice, Span, Value};

/// Represents a fully evaluated argument to a call.
#[derive(Debug, Clone)]
pub enum Argument {
    /// A positional argument
    Positional {
        span: Span,
        val: Value,
        ast: Option<Arc<Expression>>,
    },
    /// A spread argument, e.g. `...$args`
    Spread {
        span: Span,
        vals: Value,
        ast: Option<Arc<Expression>>,
    },
    /// A named argument with no value, e.g. `--flag`
    Flag {
        data: Arc<[u8]>,
        name: DataSlice,
        short: DataSlice,
        span: Span,
    },
    /// A named argument with a value, e.g. `--flag value` or `--flag=`
    Named {
        data: Arc<[u8]>,
        name: DataSlice,
        short: DataSlice,
        span: Span,
        val: Value,
        ast: Option<Arc<Expression>>,
    },
    /// Information generated by the parser for use by certain keyword commands
    ParserInfo {
        data: Arc<[u8]>,
        name: DataSlice,
        // TODO: rather than `Expression`, this would probably be best served by a specific enum
        // type for this purpose.
        info: Box<Expression>,
    },
}

impl Argument {
    /// The span encompassing the argument's usage within the call, distinct from the span of the
    /// actual value of the argument.
    pub fn span(&self) -> Option<Span> {
        match self {
            Argument::Positional { span, .. } => Some(*span),
            Argument::Spread { span, .. } => Some(*span),
            Argument::Flag { span, .. } => Some(*span),
            Argument::Named { span, .. } => Some(*span),
            // Because `ParserInfo` is generated, its span shouldn't be used
            Argument::ParserInfo { .. } => None,
        }
    }

    /// The original AST [`Expression`] for the argument's value. This is not usually available;
    /// declarations have to opt-in if they require this.
    pub fn ast_expression(&self) -> Option<&Arc<Expression>> {
        match self {
            Argument::Positional { ast, .. } => ast.as_ref(),
            Argument::Spread { ast, .. } => ast.as_ref(),
            Argument::Flag { .. } => None,
            Argument::Named { ast, .. } => ast.as_ref(),
            Argument::ParserInfo { .. } => None,
        }
    }
}

/// Stores the argument context for calls in IR evaluation.
#[derive(Debug, Clone, Default)]
pub struct ArgumentStack {
    arguments: Vec<Argument>,
}

impl ArgumentStack {
    /// Create a new, empty argument stack.
    pub const fn new() -> Self {
        ArgumentStack { arguments: vec![] }
    }

    /// Returns the index of the end of the argument stack. Call and save this before adding
    /// arguments.
    pub fn get_base(&self) -> usize {
        self.arguments.len()
    }

    /// Calculates the number of arguments past the given [previously retrieved](.get_base) base
    /// pointer.
    pub fn get_len(&self, base: usize) -> usize {
        self.arguments.len().checked_sub(base).unwrap_or_else(|| {
            panic!(
                "base ({}) is beyond the end of the arguments stack ({})",
                base,
                self.arguments.len()
            );
        })
    }

    /// Push an argument onto the end of the argument stack.
    pub fn push(&mut self, argument: Argument) {
        self.arguments.push(argument);
    }

    /// Clear all of the arguments after the given base index, to prepare for the next frame.
    pub fn leave_frame(&mut self, base: usize) {
        self.arguments.truncate(base);
    }

    /// Get arguments for the frame based on the given [`base`](`.get_base()`) and
    /// [`len`](`.get_len()`) parameters.
    pub fn get_args(&self, base: usize, len: usize) -> &[Argument] {
        &self.arguments[base..(base + len)]
    }

    /// Move arguments for the frame based on the given [`base`](`.get_base()`) and
    /// [`len`](`.get_len()`) parameters.
    pub fn drain_args(&mut self, base: usize, len: usize) -> impl Iterator<Item = Argument> + '_ {
        self.arguments.drain(base..(base + len))
    }
}
