//! Data structures for control flow emission.
use crate::{Error, Result};
use smallvec::SmallVec;
use wasmparser::BlockType;

/// The type of the control stack frame.
#[repr(u8)]
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub enum ControlStackFrameType {
    /// The if control stack frame.
    ///
    /// true is has else block, otherwise false.
    If(bool),
    /// The else control stack frame.
    Else,
    /// The loop control stack frame.
    Loop,
    /// The block control stack frame.
    Block,
}

/// Holds the necessary metadata to support the smission
/// of control flow instructions.
///
/// NOTE: The output of control flow should be placed on
/// the stack, so we don't need to store the result type.
#[derive(Clone)]
pub struct ControlStackFrame {
    /// The type of the control stack frame.
    ///
    /// If loop, break it while popping.
    pub ty: ControlStackFrameType,
    /// The program counter offset at the beginning of if.
    pub original_pc_offset: u16,
    /// The return values of the block.
    ///
    /// Could be useful for validation.
    result: BlockType,

    /// Original stack pointer.
    pub original_sp: u16,

    /// Flag to mark frames that might contain early returns
    pub might_return_early: bool,
}

impl ControlStackFrame {
    /// Create a new control stack frame.
    pub fn new(
        ty: ControlStackFrameType,
        original_pc_offset: u16,
        original_sp: u16,
        result: BlockType,
    ) -> Self {
        Self {
            ty,
            original_pc_offset,
            original_sp,
            result,
            might_return_early: false,
        }
    }

    /// Get the offset of the original program counter.
    pub fn pc_offset(&self) -> u16 {
        self.original_pc_offset
    }

    /// Get the result type of the control stack frame.
    pub fn result(&self) -> BlockType {
        self.result
    }

    /// Set the flag indicating this frame might contain early returns
    pub fn set_might_return_early(&mut self, value: bool) {
        self.might_return_early = value;
    }

    /// Check if this frame is at a function boundary
    pub fn is_function_boundary(&self) -> bool {
        // For now, we consider Block frames as potential function boundaries
        matches!(self.ty, ControlStackFrameType::Block)
    }
}

/// The control stack.
#[derive(Default)]
pub struct ControlStack {
    /// Stack frames for control flow.
    ///
    /// The 32 is set arbitrarily, we can adjust it as we see fit.
    pub stack: SmallVec<[ControlStackFrame; 32]>,
}

impl ControlStack {
    /// The total depth of the control stack.
    pub fn depth(&self) -> usize {
        self.stack.len()
    }

    /// Mark the else block of an if.
    pub fn mark_else(&mut self) -> Result<ControlStackFrame> {
        let last = self
            .stack
            .last_mut()
            .ok_or_else(|| Error::ControlStackUnderflow)?;

        if last.ty != ControlStackFrameType::If(false) {
            return Err(Error::InvalidElseBlock(last.original_pc_offset));
        }

        last.ty = ControlStackFrameType::If(true);
        Ok(last.clone())
    }

    /// Push a control stack frame.
    pub fn push(&mut self, frame: ControlStackFrame) {
        self.stack.push(frame);
    }

    /// Pop a control stack frame.
    pub fn pop(&mut self) -> Result<ControlStackFrame> {
        self.stack.pop().ok_or_else(|| Error::ControlStackUnderflow)
    }

    /// Get the label of the control stack frame at given depth.
    pub fn label_from_depth(&self, mut depth: u32) -> Result<u16> {
        for frame in self.stack.iter().rev() {
            if frame.ty == ControlStackFrameType::Else {
                continue;
            }

            if depth == 0 {
                return Ok(frame.pc_offset());
            }

            depth -= 1;
        }

        Err(Error::InvalidDepth(depth as usize))
    }

    /// Get a reference to the frame at the given depth
    pub fn frame_from_depth(&self, mut depth: u32) -> Result<&ControlStackFrame> {
        for (i, frame) in self.stack.iter().rev().enumerate() {
            if frame.ty == ControlStackFrameType::Else {
                continue;
            }

            if depth == 0 {
                return Ok(&self.stack[self.stack.len() - 1 - i]);
            }

            depth -= 1;
        }

        Err(Error::InvalidDepth(depth as usize))
    }

    /// Check if a branch at the given depth would exit the function
    pub fn is_exit_branch(&self, depth: u32) -> bool {
        // If depth exceeds our stack, it's definitely an exit
        if depth as usize >= self.depth() {
            return true;
        }

        // Get the frame that would be the target
        if let Ok(frame) = self.frame_from_depth(depth) {
            // If it's a Block at the outermost level (index 0), it might be a function boundary
            if matches!(frame.ty, ControlStackFrameType::Block) {
                for (i, f) in self.stack.iter().enumerate() {
                    if f.original_pc_offset == frame.original_pc_offset {
                        return i == 0; // It's an exit if it's the outermost block
                    }
                }
            }
        }

        false
    }

    /// Mark frames as potentially having early returns
    pub fn mark_frames_with_early_return(&mut self, depth: u32) {
        let target_idx = if depth as usize >= self.depth() {
            // If targeting beyond the stack, mark all frames
            0 // Start from the first frame
        } else {
            // Find the target frame index
            let mut target_idx = self.depth();
            let mut current_depth = 0;

            for (i, frame) in self.stack.iter().rev().enumerate() {
                if frame.ty == ControlStackFrameType::Else {
                    continue;
                }

                if current_depth == depth {
                    target_idx = self.depth() - 1 - i;
                    break;
                }

                current_depth += 1;
            }

            target_idx
        };

        // Mark frames from target to the end
        for i in target_idx..self.depth() {
            self.stack[i].set_might_return_early(true);
        }
    }

    /// Get the return type of the control stack frame at given depth.
    pub fn ret_ty(&self, depth: usize) -> Result<BlockType> {
        if depth == 0 {
            return Err(Error::InvalidDepth(depth));
        }

        self.stack
            .get(self.depth() - depth)
            .map(|f| f.result)
            .ok_or_else(|| Error::InvalidDepth(depth))
    }

    /// Get the type of the control stack frame at given depth.
    pub fn ty(&self, depth: usize) -> Result<ControlStackFrameType> {
        if depth == 0 {
            return Err(Error::InvalidDepth(depth));
        }

        self.stack
            .get(self.depth() - depth)
            .map(|f| f.ty)
            .ok_or_else(|| Error::InvalidDepth(depth))
    }

    /// Get the length of the control stack
    pub fn len(&self) -> usize {
        self.stack.len()
    }

    /// Check if the control stack is empty
    pub fn is_empty(&self) -> bool {
        self.stack.is_empty()
    }
}