/*
 * Copyright (c) Meta Platforms, Inc. and affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 */

use std::fmt::Display;

use indexmap::{IndexMap, IndexSet};

use crate::id_types::BlockId;
use crate::{Identifier, InstrIx, Terminal};

/// Represents a sequence of instructions that will always[1] execute
/// consecutively. Concretely, a block may have zero or more instructions
/// and ends with a terminal node that describes where control flow may
/// continue.
///
/// [1] Assuming no exceptions are thrown.
#[derive(Debug)]
pub struct BasicBlock {
    /// The identifier for the block
    pub id: BlockId,

    /// What kind of block this is. Used to distinguish basic blocks that
    /// correspond to a block scope in the input from basic blocks that
    /// represent control flow within statements or expressions, such as
    /// loops, logicals, ternaries, optionals, or sequences.
    pub kind: BlockKind,

    /// The ordered instructions in this block
    pub instructions: Vec<InstrIx>,

    /// The terminal instruction for the block
    pub terminal: Terminal,

    /// The immediate predecessors of this block
    pub predecessors: IndexSet<BlockId>,

    pub phis: Vec<Phi>,
}

#[derive(Debug)]
pub struct Phi {
    pub identifier: Identifier,
    pub operands: IndexMap<BlockId, Identifier>,
}

#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub enum BlockKind {
    Block,
    Value,
    Loop,
    Sequence,
}

impl Display for BlockKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Block => f.write_str("block"),
            Self::Value => f.write_str("value"),
            Self::Loop => f.write_str("loop"),
            Self::Sequence => f.write_str("sequence"),
        }
    }
}