use std::collections::HashMap;

use crate::frontend::ast::{BinaryOp, UnaryOp};
use crate::frontend::sema::Ty;
use crate::frontend::token::Span;

/// A strongly-typed reference to a basic block.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BlockId(pub usize);

/// A strongly-typed reference to a local variable or temporary.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct LocalId(pub usize);

#[derive(Debug)]
pub struct MirModule {
    pub structs: HashMap<String, Vec<(String, Ty)>>,
    pub extern_functions: Vec<MirExternFunction>,
    pub functions: Vec<MirFunction>,
}

#[derive(Debug)]
pub struct MirExternFunction {
    pub name: String,
    pub params: Vec<Ty>,
    pub return_type: Ty,
}

#[derive(Debug)]
pub struct MirFunction {
    pub name: String,
    pub params: Vec<LocalId>,
    pub return_type: Ty,
    pub locals: Vec<LocalDecl>,
    pub blocks: Vec<BasicBlock>,
}

/// A declaration of a local variable or compiler-generated temporary.
#[derive(Debug)]
pub struct LocalDecl {
    pub id: LocalId,
    pub ty: Ty,
    pub mutable: bool,
    /// Contains the name if it is a user-declared variable, or `None` if it's a compiler temporary.
    pub name: Option<String>,
    /// Indicates if the address of this local was ever taken, forcing allocation via explicit memory stack slots instead of SSA pseudo-registers.
    pub address_taken: bool,
}

/// A sequential list of non-branching statements followed by a single terminator.
#[derive(Debug)]
pub struct BasicBlock {
    pub id: BlockId,
    pub statements: Vec<Statement>,
    pub terminator: Terminator,
}

#[derive(Debug)]
pub struct Statement {
    pub kind: StatementKind,
    pub span: Span,
}

#[derive(Debug)]
pub enum StatementKind {
    /// Assigns the result of an Rvalue to a local variable or temporary.
    Assign(LocalId, Rvalue),
    /// Executes an Rvalue strictly for its side effects (e.g. FFI calling `Unit` functions)
    SideEffect(Rvalue),
    /// Stores a value into the memory address specified by the pointer operand.
    Store { ptr: Operand, val: Rvalue },
}

/// Operations that produce a value.
#[derive(Debug)]
pub enum Rvalue {
    Use(Operand),
    UnaryOp(UnaryOp, Operand),
    BinaryOp(BinaryOp, Operand, Operand),
    Cast(Ty, Operand),
    Call(String, Vec<Operand>, Ty),
    AddressOf(LocalId),
    ReadPointer(Operand),
    GetFieldPtr {
        base_ptr: Operand,
        struct_name: String,
        field_name: String,
    },
    GetElementPtr {
        base_ptr: Operand,
        index: Operand,
        element_ty: Ty,
    },
}

/// An atomic value used as inputs to instructions.
#[derive(Debug, Clone)]
pub enum Operand {
    Copy(LocalId),
    Constant(ConstantValue),
}

#[derive(Debug, Clone)]
pub enum ConstantValue {
    Integer(u64, Ty),
    Float(f64, Ty),
    Boolean(bool),
}

#[derive(Debug)]
pub struct Terminator {
    pub kind: TerminatorKind,
    pub span: Span,
}

#[derive(Debug)]
pub enum TerminatorKind {
    Goto {
        target: BlockId,
    },
    CondBranch {
        cond: Operand,
        target_true: BlockId,
        target_false: BlockId,
    },
    Return {
        value: Option<Operand>,
    },
    Unreachable,
}
