use std::{borrow::Cow, fmt::Write, rc::Rc};
use datapack_common::vfs::Directory;
use itertools::Itertools;
use debris_common::CompileContext;
use debris_llir::minecraft_utils::Scoreboard;
use debris_llir::{
    block_id::BlockId,
    extern_item_path::ExternItemPath,
    item_id::ItemId,
    llir_nodes::{
        BinaryOperation, Branch, BranchKind, Call, Condition, ExecuteRaw, ExecuteRawComponent,
        FastStore, FastStoreFromResult, Function, Node, VariableAccess, WriteMessage,
    },
    minecraft_utils::{ScoreboardOperation, ScoreboardValue},
    CallGraph, CodeStats, Llir,
};
use rustc_hash::FxHashSet;
use crate::{
    common::{
        ExecuteComponent, MinecraftCommand, MinecraftRange, ObjectiveCriterion, ScoreboardPlayer,
    },
    DatapackBackend,
};
use super::{
    function_context::{FunctionContext, FunctionLocation},
    json_formatter::format_json,
    scoreboard_constants::ScoreboardConstants,
    scoreboard_context::ScoreboardContext,
    Datapack,
};
#[derive(Debug)]
pub struct DatapackGenerator<'a> {
    compile_context: &'a CompileContext,
    llir: &'a Llir,
    function_ctx: FunctionContext,
    stack: Vec<Vec<MinecraftCommand>>,
    scoreboard_ctx: ScoreboardContext,
    scoreboard_constants: ScoreboardConstants,
}
impl<'a> DatapackGenerator<'a> {
    fn add_command(&mut self, command: MinecraftCommand) {
        if let MinecraftCommand::ScoreboardSetFromResult {
            command: nested_command,
            player,
        } = &command
        {
            if let MinecraftCommand::Execute { parts, and_then } = &**nested_command {
                let mut parts = parts;
                let mut and_then = and_then;
                let mut condition_count = 0;
                while condition_count < 2 {
                    condition_count += parts.iter().filter(|part| part.is_condition()).count();
                    if let Some(MinecraftCommand::Execute {
                        and_then: next_and_then,
                        parts: next_parts,
                    }) = and_then.as_deref()
                    {
                        parts = next_parts;
                        and_then = next_and_then;
                    } else {
                        break;
                    }
                }
                if condition_count >= 2 {
                    let player = player.clone();
                    self.add_command(MinecraftCommand::ScoreboardSet { player, value: 0 });
                }
            }
        }
        self.stack.last_mut().expect("Empty stack").push(command);
    }
    fn catch_output(&mut self, node: &Node) -> Vec<MinecraftCommand> {
        self.stack.push(Vec::new());
        self.handle(node);
        self.stack.pop().unwrap()
    }
    fn handle_main_function(&mut self, block_ids: impl Iterator<Item = BlockId>) -> bool {
        let function_location = FunctionLocation::Main {
            function_name: "main".to_string(),
        };
        let function_id = self.function_ctx.reserve_at(&function_location);
        self.stack.push(Vec::new());
        self.stack.push(Vec::new());
        for id in block_ids {
            self.handle_call(&Call { id });
        }
        let mut user_content = self.stack.pop().unwrap();
        {
            let scoreboard_commands: Vec<_> = self
                .scoreboard_ctx
                .scoreboards
                .values()
                .flat_map(|scoreboard_name| {
                    [
                        MinecraftCommand::ScoreboardRemove {
                            name: scoreboard_name.clone(),
                        },
                        MinecraftCommand::ScoreboardAdd {
                            name: scoreboard_name.clone(),
                            criterion: ObjectiveCriterion::Dummy,
                            json_name: None,
                        },
                    ]
                })
                .collect();
            for command in scoreboard_commands {
                self.add_command(command);
            }
            for constant in self.scoreboard_constants.constants().collect::<Vec<_>>() {
                let player = self
                    .scoreboard_constants
                    .get_name(constant, &mut self.scoreboard_ctx);
                self.add_command(MinecraftCommand::ScoreboardSet {
                    player,
                    value: constant,
                });
            }
        }
        self.stack.last_mut().unwrap().append(&mut user_content);
        let nodes = self.stack.pop().unwrap();
        let generate = !nodes.is_empty();
        if generate {
            self.function_ctx.insert(function_id, nodes.into_iter());
        }
        generate
    }
    fn handle_ticking_function(&mut self, block_ids: impl Iterator<Item = BlockId>) -> bool {
        let function_location = FunctionLocation::Main {
            function_name: "tick".to_string(),
        };
        let function_id = self.function_ctx.reserve_at(&function_location);
        self.stack.push(Vec::new());
        for id in block_ids {
            self.handle(&Node::Call(Call { id }));
        }
        let nodes = self.stack.pop().unwrap();
        let generate = !nodes.is_empty();
        if generate {
            self.function_ctx.insert(function_id, nodes.into_iter());
        }
        generate
    }
    fn handle_extern_functions(&mut self, path: ExternItemPath, id: BlockId) {
        let function_location = FunctionLocation::Custom { path };
        let function_id = self.function_ctx.reserve_at(&function_location);
        self.stack.push(Vec::new());
        self.handle(&Node::Call(Call { id }));
        let nodes = self.stack.pop().unwrap();
        let generate = !nodes.is_empty();
        if generate {
            self.function_ctx.insert(function_id, nodes.into_iter());
        }
    }
    fn handle(&mut self, node: &Node) {
        match node {
            Node::FastStore(fast_store) => self.handle_fast_store(fast_store),
            Node::FastStoreFromResult(fast_store_from_result) => {
                self.handle_fast_store_from_result(fast_store_from_result);
            }
            Node::BinaryOperation(bin_op) => self.handle_binary_operation(bin_op),
            Node::Call(call) => self.handle_call(call),
            Node::Condition(condition) => self.handle_condition(condition),
            Node::Execute(execute) => self.handle_execute(execute),
            Node::Write(write) => self.handle_write(write),
            Node::Branch(branch) => self.handle_branch(branch),
            Node::Nop => (),
        }
    }
    fn handle_function(&mut self, function: &Function) {
        let id = self.function_ctx.reserve_block(function.id);
        self.stack.push(Vec::new());
        for node in function.nodes() {
            self.handle(node);
        }
        let nodes = self.stack.pop().unwrap();
        self.function_ctx.insert(id, nodes.into_iter());
    }
    fn handle_fast_store(&mut self, fast_store: &FastStore) {
        let value = &fast_store.value;
        let scoreboard = self.scoreboard_ctx.get_scoreboard(Scoreboard::Main);
        let command = match value {
            ScoreboardValue::Static(static_value) => MinecraftCommand::ScoreboardSet {
                player: ScoreboardPlayer {
                    player: self.scoreboard_ctx.get_scoreboard_player(fast_store.id),
                    scoreboard,
                },
                value: *static_value,
            },
            ScoreboardValue::Scoreboard(other_player) => MinecraftCommand::ScoreboardSetEqual {
                player1: ScoreboardPlayer {
                    player: self.scoreboard_ctx.get_scoreboard_player(fast_store.id),
                    scoreboard: Rc::clone(&scoreboard),
                },
                player2: ScoreboardPlayer {
                    player: self.scoreboard_ctx.get_scoreboard_player(*other_player),
                    scoreboard,
                },
            },
        };
        self.add_command(command);
    }
    fn handle_fast_store_from_result(&mut self, fast_store_from_result: &FastStoreFromResult) {
        let mut inner_commands = self.catch_output(&fast_store_from_result.command);
        let Some(last) = inner_commands.pop() else {
            panic!("Expected at least one inner function, but got None");
        };
        for command in inner_commands {
            self.add_command(command);
        }
        let command = MinecraftCommand::ScoreboardSetFromResult {
            player: ScoreboardPlayer {
                player: self
                    .scoreboard_ctx
                    .get_scoreboard_player(fast_store_from_result.id),
                scoreboard: self.scoreboard_ctx.get_scoreboard(Scoreboard::Main),
            },
            command: Box::new(last),
        };
        self.add_command(command);
    }
    fn handle_binary_operation(&mut self, binary_operation: &BinaryOperation) {
        let scoreboard = self.scoreboard_ctx.get_scoreboard(Scoreboard::Main);
        let (lhs_player, rhs_player, overwrite_lhs) =
            match (binary_operation.lhs, binary_operation.rhs) {
                (ScoreboardValue::Static(lhs), ScoreboardValue::Static(rhs)) => {
                    let result = binary_operation.operation.evaluate(lhs, rhs);
                    let player = self
                        .scoreboard_ctx
                        .get_scoreboard_player(binary_operation.id);
                    let command = MinecraftCommand::ScoreboardSet {
                        player: ScoreboardPlayer {
                            player,
                            scoreboard: Rc::clone(&scoreboard),
                        },
                        value: result,
                    };
                    self.add_command(command);
                    return;
                }
                (ScoreboardValue::Static(lhs_val), ScoreboardValue::Scoreboard(rhs_id)) => {
                    if binary_operation.id == rhs_id {
                        let lhs_player = self.scoreboard_ctx.get_temporary_player();
                        let rhs_id = self.scoreboard_ctx.get_scoreboard_player(rhs_id);
                        self.add_command(MinecraftCommand::ScoreboardSet {
                            player: lhs_player.clone(),
                            value: lhs_val,
                        });
                        self.add_command(MinecraftCommand::ScoreboardOperation {
                            operation: binary_operation.operation,
                            player1: lhs_player,
                            player2: ScoreboardPlayer {
                                player: rhs_id,
                                scoreboard: Rc::clone(&scoreboard),
                            },
                        });
                        return;
                    }
                    let lhs_id = self
                        .scoreboard_ctx
                        .get_scoreboard_player(binary_operation.id);
                    self.add_command(MinecraftCommand::ScoreboardSet {
                        player: ScoreboardPlayer {
                            player: lhs_id.clone(),
                            scoreboard: Rc::clone(&scoreboard),
                        },
                        value: lhs_val,
                    });
                    (
                        lhs_id,
                        self.scoreboard_ctx.get_scoreboard_player(rhs_id),
                        true,
                    )
                }
                (ScoreboardValue::Scoreboard(lhs_id), ScoreboardValue::Scoreboard(rhs_id)) => (
                    self.scoreboard_ctx.get_scoreboard_player(lhs_id),
                    self.scoreboard_ctx.get_scoreboard_player(rhs_id),
                    lhs_id == binary_operation.id,
                ),
                (ScoreboardValue::Scoreboard(lhs_id), ScoreboardValue::Static(value))
                    if (binary_operation.operation == ScoreboardOperation::Plus
                        || binary_operation.operation == ScoreboardOperation::Minus) =>
                {
                    let real_value = if binary_operation.operation == ScoreboardOperation::Minus {
                        -value
                    } else {
                        value
                    };
                    let player = if lhs_id == binary_operation.id {
                        self.scoreboard_ctx.get_scoreboard_player(lhs_id)
                    } else {
                        let player = self
                            .scoreboard_ctx
                            .get_scoreboard_player(binary_operation.id);
                        let lhs_player = self.scoreboard_ctx.get_scoreboard_player(lhs_id);
                        self.add_command(MinecraftCommand::ScoreboardSetEqual {
                            player1: ScoreboardPlayer {
                                player: player.clone(),
                                scoreboard: Rc::clone(&scoreboard),
                            },
                            player2: ScoreboardPlayer {
                                player: lhs_player,
                                scoreboard: Rc::clone(&scoreboard),
                            },
                        });
                        player
                    };
                    self.add_command(MinecraftCommand::ScoreboardOperationAdd {
                        player: ScoreboardPlayer { player, scoreboard },
                        value: real_value,
                    });
                    return;
                }
                (ScoreboardValue::Scoreboard(lhs_id), ScoreboardValue::Static(value)) => {
                    let ScoreboardPlayer {
                        player,
                        scoreboard: _,
                    } = self
                        .scoreboard_constants
                        .get_name(value, &mut self.scoreboard_ctx);
                    (
                        self.scoreboard_ctx.get_scoreboard_player(lhs_id),
                        player,
                        lhs_id == binary_operation.id,
                    )
                }
            };
        if !overwrite_lhs {
            let player1 = self
                .scoreboard_ctx
                .get_scoreboard_player(binary_operation.id);
            let player2 = lhs_player;
            self.add_command(MinecraftCommand::ScoreboardSetEqual {
                player1: ScoreboardPlayer {
                    player: player1,
                    scoreboard: Rc::clone(&scoreboard),
                },
                player2: ScoreboardPlayer {
                    player: player2,
                    scoreboard: Rc::clone(&scoreboard),
                },
            });
        }
        let player1 = self
            .scoreboard_ctx
            .get_scoreboard_player(binary_operation.id);
        self.add_command(MinecraftCommand::ScoreboardOperation {
            player1: ScoreboardPlayer {
                player: player1,
                scoreboard: Rc::clone(&scoreboard),
            },
            player2: ScoreboardPlayer {
                player: rhs_player,
                scoreboard: Rc::clone(&scoreboard),
            },
            operation: binary_operation.operation,
        });
    }
    fn handle_condition(&mut self, condition: &Condition) {
        let condition = self.get_condition(condition, None);
        self.add_command(condition);
    }
    fn branch_taken_hack_required(&self, branch: &Branch) -> Option<BranchKind> {
        fn can_node_write_to_any_of(
            stats: &CodeStats,
            node: &Node,
            options: &FxHashSet<ItemId>,
        ) -> bool {
            let mut found_write = false;
            node.variable_accesses(&mut |access| match access {
                VariableAccess::Write(id, _)
                | VariableAccess::ReadWrite(ScoreboardValue::Scoreboard(id))
                    if options.contains(id) =>
                {
                    found_write = true;
                }
                _ => {}
            });
            if !found_write {
                node.scan(&mut |inner| {
                    if let Node::Call(Call { id }) = inner {
                        if !found_write {
                            found_write = stats.check_function_can_write_to(&[*id], options);
                        }
                    }
                });
            }
            found_write
        }
        let options = branch.condition.variable_reads();
        let pos_can_write =
            can_node_write_to_any_of(&self.llir.stats, &branch.pos_branch, &options);
        let neg_can_write =
            can_node_write_to_any_of(&self.llir.stats, &branch.neg_branch, &options);
        match (pos_can_write, neg_can_write) {
            (false, false) => None,
            (false, true) => Some(BranchKind::NegBranch),
            (true, false) => Some(BranchKind::PosBranch),
            (true, true) => Some(BranchKind::Both),
        }
    }
    fn handle_branch(&mut self, branch: &Branch) {
        let pos_branch = self.catch_output(&branch.pos_branch);
        let pos_branch = self.get_as_single_command(pos_branch);
        let neg_branch = self.catch_output(&branch.neg_branch);
        let neg_branch = self.get_as_single_command(neg_branch);
        let condition = Cow::Borrowed(&branch.condition);
        let bad_branch = self.branch_taken_hack_required(branch);
        let branch_taken_flag = (bad_branch == Some(BranchKind::Both)).then(|| {
            let player = self.scoreboard_ctx.get_temporary_player();
            self.add_command(MinecraftCommand::ScoreboardSet {
                player: player.clone(),
                value: 0,
            });
            player
        });
        if neg_branch.is_some() && pos_branch.is_some() && !branch.condition.is_simple() {
            let (pos_branch_value, condition) = if matches!(condition.as_ref(), Condition::Or(_)) {
                (0, Cow::Owned(condition.not()))
            } else {
                (1, condition)
            };
            let cached_condition = self.get_condition(&condition, None);
            let player = self.scoreboard_ctx.get_temporary_player();
            self.add_command(MinecraftCommand::ScoreboardSetFromResult {
                command: cached_condition.into(),
                player: player.clone(),
            });
            let pos_and_then = pos_branch.map(|cmd| {
                if let Some(player) = &branch_taken_flag {
                    self.extend_command_by(
                        cmd,
                        MinecraftCommand::ScoreboardSet {
                            player: player.clone(),
                            value: 1,
                        },
                    )
                } else {
                    cmd
                }
            });
            let pos_command = MinecraftCommand::Execute {
                parts: vec![ExecuteComponent::IfScoreRange {
                    player: player.clone(),
                    range: MinecraftRange::Equal(pos_branch_value),
                }],
                and_then: pos_and_then.map(Box::new),
            };
            let mut neg_command_parts = Vec::with_capacity(2);
            if let Some(player) = branch_taken_flag {
                neg_command_parts.push(ExecuteComponent::IfScoreRange {
                    player,
                    range: MinecraftRange::Equal(0),
                });
            }
            neg_command_parts.push(ExecuteComponent::IfScoreRange {
                player,
                range: MinecraftRange::NotEqual(pos_branch_value),
            });
            let neg_command = MinecraftCommand::Execute {
                parts: neg_command_parts,
                and_then: neg_branch.map(Box::new),
            };
            let (first_check, second_check) = if bad_branch == Some(BranchKind::PosBranch) {
                (neg_command, pos_command)
            } else {
                (pos_command, neg_command)
            };
            self.add_command(first_check);
            self.add_command(second_check);
        } else {
            let mut pos_command = None;
            let mut neg_command = None;
            if let Some(mut and_then) = pos_branch {
                if let Some(player) = branch_taken_flag.clone() {
                    and_then = self.extend_command_by(
                        and_then,
                        MinecraftCommand::ScoreboardSet { player, value: 1 },
                    );
                }
                let and_then_command = self.get_condition(&condition, Some(and_then));
                pos_command = Some(and_then_command);
            }
            if let Some(and_then) = neg_branch {
                let condition = condition.not();
                let mut parts = Vec::new();
                if let Some(player) = branch_taken_flag {
                    parts.push(ExecuteComponent::IfScoreRange {
                        player,
                        range: MinecraftRange::Equal(0),
                    });
                }
                self.get_condition_inner(&condition, &mut parts);
                let and_then_command = MinecraftCommand::Execute {
                    parts,
                    and_then: Some(Box::new(and_then)),
                };
                neg_command = Some(and_then_command);
            }
            let (first_check, second_check) = if bad_branch == Some(BranchKind::PosBranch) {
                (neg_command, pos_command)
            } else {
                (pos_command, neg_command)
            };
            if let Some(first_check) = first_check {
                self.add_command(first_check);
            }
            if let Some(second_check) = second_check {
                self.add_command(second_check);
            }
        };
    }
    fn handle_call(&mut self, call: &Call) {
        let num_calls = self.llir.stats.function_calls[&call.id];
        if num_calls == 1 {
            if let Some(function_id) = self.function_ctx.get_function_id(call.id) {
                let ident = self.function_ctx.get_function_ident(function_id);
                self.add_command(MinecraftCommand::Function { function: ident });
            } else {
                let function = self
                    .llir
                    .functions
                    .values()
                    .find(|func| func.id == call.id)
                    .expect("Missing function");
                for node in function.nodes() {
                    self.handle(node);
                }
            }
        } else {
            if self.function_ctx.get_function_id(call.id).is_none() {
                self.function_ctx.reserve_block(call.id);
            }
            let function_id = self.function_ctx.get_function_id(call.id).unwrap();
            let ident = self.function_ctx.get_function_ident(function_id);
            self.add_command(MinecraftCommand::Function { function: ident });
        }
    }
    fn handle_execute(&mut self, execute: &ExecuteRaw) {
        let command = {
            let mut command = String::new();
            for part in &execute.0 {
                match part {
                    ExecuteRawComponent::String(val) => {
                        command.push_str(val);
                    }
                    ExecuteRawComponent::ScoreboardValue(val) => match val {
                        ScoreboardValue::Static(val) => {
                            command.push_str(&val.to_string());
                        }
                        ScoreboardValue::Scoreboard(id) => write!(
                            command,
                            "{} {}",
                            self.scoreboard_ctx.get_scoreboard_player(*id),
                            self.scoreboard_ctx.get_scoreboard(Scoreboard::Main)
                        )
                        .unwrap(),
                    },
                    ExecuteRawComponent::Node(node) => {
                        let function_commands = self.catch_output(node);
                        let call = self.get_as_single_command(function_commands);
                        if let Some(call) = call {
                            write!(command, "{call}").unwrap();
                        }
                    }
                }
            }
            command
        };
        self.add_command(MinecraftCommand::RawCommand {
            command: command.into(),
        });
    }
    fn handle_write(&mut self, write: &WriteMessage) {
        let message = format_json(
            &write.message,
            &mut self.scoreboard_ctx,
            &mut self.function_ctx,
        );
        self.add_command(MinecraftCommand::JsonMessage {
            target: write.target,
            message,
        });
    }
    fn get_condition(
        &mut self,
        condition: &Condition,
        and_then: Option<MinecraftCommand>,
    ) -> MinecraftCommand {
        let mut parts = Vec::new();
        self.get_condition_inner(condition, &mut parts);
        MinecraftCommand::Execute {
            parts,
            and_then: and_then.map(Box::new),
        }
    }
    fn get_condition_inner(&mut self, condition: &Condition, parts: &mut Vec<ExecuteComponent>) {
        match condition {
            Condition::Compare {
                lhs,
                rhs,
                comparison,
            } => match (lhs, rhs) {
                (ScoreboardValue::Scoreboard(lhs_id), ScoreboardValue::Scoreboard(rhs_id)) => parts
                    .push(ExecuteComponent::IfScoreRelation {
                        comparison: *comparison,
                        player1: ScoreboardPlayer {
                            player: self.scoreboard_ctx.get_scoreboard_player(*lhs_id),
                            scoreboard: self.scoreboard_ctx.get_scoreboard(Scoreboard::Main),
                        },
                        player2: ScoreboardPlayer {
                            player: self.scoreboard_ctx.get_scoreboard_player(*rhs_id),
                            scoreboard: self.scoreboard_ctx.get_scoreboard(Scoreboard::Main),
                        },
                    }),
                (ScoreboardValue::Static(lhs), ScoreboardValue::Static(rhs)) => {
                    let lhs = self
                        .scoreboard_constants
                        .get_name(*lhs, &mut self.scoreboard_ctx);
                    let rhs = self
                        .scoreboard_constants
                        .get_name(*rhs, &mut self.scoreboard_ctx);
                    parts.push(ExecuteComponent::IfScoreRelation {
                        comparison: *comparison,
                        player1: lhs,
                        player2: rhs,
                    });
                }
                (lhs, rhs) => {
                    let (id, static_value, operator) = match (lhs, rhs) {
                        (
                            ScoreboardValue::Static(static_value),
                            ScoreboardValue::Scoreboard(id),
                        ) => (id, static_value, comparison.flip_sides()),
                        (
                            ScoreboardValue::Scoreboard(id),
                            ScoreboardValue::Static(static_value),
                        ) => (id, static_value, *comparison),
                        (_, _) => unreachable!(
                            "Verified that there is one static and one scoreboard value"
                        ),
                    };
                    parts.push(ExecuteComponent::IfScoreRange {
                        player: ScoreboardPlayer {
                            player: self.scoreboard_ctx.get_scoreboard_player(*id),
                            scoreboard: self.scoreboard_ctx.get_scoreboard(Scoreboard::Main),
                        },
                        range: MinecraftRange::from_operator(*static_value, operator),
                    });
                }
            },
            Condition::And(conditions) => {
                for condition in conditions {
                    self.get_condition_inner(condition, parts);
                }
            }
            Condition::Or(conditions) => {
                let mut neg_parts = Vec::with_capacity(conditions.len());
                for condition in conditions {
                    self.get_condition_inner(&condition.not(), &mut neg_parts);
                }
                let neg_condition = MinecraftCommand::Execute {
                    and_then: None,
                    parts: neg_parts,
                };
                let temp_score = self.scoreboard_ctx.get_temporary_player();
                self.add_command(MinecraftCommand::ScoreboardSetFromResult {
                    player: temp_score.clone(),
                    command: Box::new(neg_condition),
                });
                parts.push(ExecuteComponent::IfScoreRange {
                    player: temp_score,
                    range: MinecraftRange::Equal(0),
                });
            }
        }
    }
    fn get_as_single_command(
        &mut self,
        commands: Vec<MinecraftCommand>,
    ) -> Option<MinecraftCommand> {
        match commands.as_slice() {
            [] => None,
            [_] => Some(commands.into_iter().next().unwrap()),
            _ => {
                let function = self.function_ctx.reserve();
                self.function_ctx.insert(function, commands.into_iter());
                Some(MinecraftCommand::Function {
                    function: self.function_ctx.get_function_ident(function),
                })
            }
        }
    }
    fn extend_command_by(
        &mut self,
        command: MinecraftCommand,
        other: MinecraftCommand,
    ) -> MinecraftCommand {
        let id = if let MinecraftCommand::Function { function } = command {
            self.function_ctx.get_function_id_from_ident(&function)
        } else {
            let id = self.function_ctx.reserve();
            self.function_ctx.insert(id, std::iter::once(command));
            id
        };
        let function = self.function_ctx.get_function_ident(id);
        self.function_ctx.append_to_fn(id, other);
        MinecraftCommand::Function { function }
    }
    pub fn new(ctx: &'a CompileContext, llir: &'a Llir) -> Self {
        let function_namespace = Rc::from(ctx.config.project_name.to_lowercase());
        let scoreboard_ctx = ScoreboardContext::new(
            ctx.config.default_scoreboard_name.clone(),
            ctx.config.build_mode,
        );
        DatapackGenerator {
            compile_context: ctx,
            llir,
            function_ctx: FunctionContext::new(function_namespace),
            stack: Default::default(),
            scoreboard_ctx,
            scoreboard_constants: Default::default(),
        }
    }
    pub fn build(mut self) -> Directory {
        let mut pack = Datapack::new(&self.compile_context.config);
        let mut call_graph = CallGraph::from(&self.llir.functions);
        for function in call_graph.iter_dfs(self.llir.runtime.root_blocks()) {
            if !self.llir.runtime.contains(&function) {
                let function = self.llir.functions.get(&function).unwrap();
                self.handle_function(function);
            }
        }
        let tick_json =
            self.handle_ticking_function(self.llir.runtime.scheduled_blocks.iter().copied());
        let load_json = self.handle_main_function(self.llir.runtime.load_blocks.iter().copied());
        for (id, name) in &self.llir.runtime.extern_blocks {
            self.handle_extern_functions(name.clone(), *id);
        }
        if tick_json {
            pack.add_tick_json(&self.compile_context.config);
        }
        if load_json {
            pack.add_load_json(&self.compile_context.config);
        }
        let function_base_dir = pack.functions();
        for (function_ident, function) in self.function_ctx.into_functions() {
            let contents = function.iter().fold(String::new(), |mut prev, next| {
                writeln!(prev, "{next}").unwrap();
                prev
            });
            let file = {
                let mut file_name = function_ident.path.as_str();
                let mut dir = &mut *function_base_dir;
                for (dirname, next) in function_ident.path.split('/').tuple_windows() {
                    file_name = next;
                    dir = dir.dir(dirname.to_string());
                }
                dir.file(format!("{}{}", file_name, DatapackBackend::FILE_EXTENSION))
            };
            file.push_string(&contents);
        }
        pack.dir
    }
}