#![allow(dead_code, unused)]
#![allow(
    clippy::all,
    clippy::pedantic,
    clippy::nursery,
    clippy::cargo,
    clippy::print_stdout,
    clippy::print_stderr,
    clippy::empty_structs_with_brackets,
    clippy::semicolon_outside_block,
    clippy::unreachable,
    clippy::undocumented_unsafe_blocks,
    clippy::needless_return,
    clippy::collapsible_if
)]

use heck::ToPascalCase;
use proc_macro2::TokenStream;
use quote::{format_ident, quote};
use rayon::prelude::*;
use std::{
    fs,
    io::Write,
    path::Path,
    process::{Command, Stdio},
};

mod advancement;
mod attributes;
mod bedrock_biome;
mod bedrock_creative;
mod biome;
mod bitsets;
mod block;
mod carver;
pub mod chest_loot;
mod chunk_gen_settings;
mod chunk_status;
mod chunk_view_lut;
mod composter_increase_chance;
mod configured_feature;
mod damage_type;
mod data_component;
mod dimension;
mod effect;
mod enchantments;
mod entity_pose;
mod entity_status;
mod entity_type;
mod flower_pot_transformations;
mod fluid;
mod fuels;
mod game_event;
mod game_rules;
mod item;
mod jukebox_song;
pub mod loot;
mod message_type;
mod meta_data_type;
mod noise_parameter;
mod noise_router;
mod packet;
mod particle;
mod placed_feature;
mod potion;
mod potion_brewing;
mod recipe_remainder;
mod recipes;
mod registry;
mod remap;
mod scoreboard_slot;
mod screen;
mod sound;
mod sound_category;
mod spawn_egg;
mod statistic;
mod structures;
mod tag;
mod tracked_data;
mod translations;
mod version;
mod villager;
mod wit;
mod world_event;

/// Output directory where all generated Rust source files are written.
pub const OUT_DIR: &str = "../../crates/pumpkin-data/src/generated";

/// Entry point for the code generator. Runs all registered builder functions in parallel
/// and writes their output to [`OUT_DIR`].
pub fn main() {
    let manifest_dir = std::path::Path::new(env!("CARGO_MANIFEST_DIR"));
    std::env::set_current_dir(manifest_dir).expect("Failed to set current dir to manifest dir");

    type BuilderFn = fn() -> TokenStream;

    fs::create_dir_all(OUT_DIR).expect("Failed to create output directory");

    let mut build_functions: Vec<(BuilderFn, &str)> = vec![
        (advancement::build, "advancement.rs"),
        (bedrock_biome::build, "bedrock_biome.rs"),
        (bedrock_creative::build, "bedrock_creative.rs"),
        (packet::build, "packet.rs"),
        (screen::build, "screen.rs"),
        (particle::build, "particle.rs"),
        (sound::build, "sound.rs"),
        (meta_data_type::build, "meta_data_type.rs"),
        (tracked_data::build, "tracked_data.rs"),
        (chunk_status::build, "chunk_status.rs"),
        (chunk_view_lut::build, "chunk_view_lut.rs"),
        (game_event::build, "game_event.rs"),
        (game_rules::build, "game_rules.rs"),
        (registry::build, "registry.rs"),
        (dimension::build, "dimension.rs"),
        (translations::build, "translation.rs"),
        (jukebox_song::build, "jukebox_song.rs"),
        (sound_category::build, "sound_category.rs"),
        (entity_pose::build, "entity_pose.rs"),
        (scoreboard_slot::build, "scoreboard_slot.rs"),
        (world_event::build, "world_event.rs"),
        (entity_type::build, "entity_type.rs"),
        (statistic::build, "statistic.rs"),
        (noise_parameter::build, "noise_parameter.rs"),
        (biome::build, "biome.rs"),
        (damage_type::build, "damage_type.rs"),
        (message_type::build, "message_type.rs"),
        (spawn_egg::build, "spawn_egg.rs"),
        (block::build, "block.rs"),
        (item::build, "item.rs"),
        (structures::build, "structures.rs"),
        (chunk_gen_settings::build, "chunk_gen_settings.rs"),
        (fluid::build, "fluid.rs"),
        (entity_status::build, "entity_status.rs"),
        (tag::build, "tag.rs"),
        (noise_router::build, "noise_router.rs"),
        (villager::build, "villager.rs"),
        (
            flower_pot_transformations::build,
            "flower_pot_transformations.rs",
        ),
        (
            composter_increase_chance::build,
            "composter_increase_chance.rs",
        ),
        (recipes::build, "recipes.rs"),
        (enchantments::build, "enchantment.rs"),
        (fuels::build, "fuels.rs"),
        (data_component::build, "data_component.rs"),
        (attributes::build, "attributes.rs"),
        (effect::build, "effect.rs"),
        (potion::build, "potion.rs"),
        (potion_brewing::build, "potion_brewing.rs"),
        (recipe_remainder::build, "recipe_remainder.rs"),
        (placed_feature::build_enum, "placed_feature.rs"),
        (placed_feature::build, "placed_features_generated.rs"),
        (configured_feature::build_enum, "configured_feature.rs"),
        (
            configured_feature::build,
            "configured_features_generated.rs",
        ),
        (carver::build, "carver.rs"),
        (chest_loot::build, "chest_loot.rs"),
    ];
    build_functions.extend(remap::build());

    // If any arguments are given, treat them as file-stem filters.
    // e.g. `cargo run -- chest_loot` only regenerates chest_loot.rs.
    let filters: Vec<String> = std::env::args().skip(1).collect();
    let build_functions: Vec<_> = if filters.is_empty() {
        wit::main();
        build_functions
    } else {
        build_functions
            .into_iter()
            .filter(|(_, file)| {
                let stem = file.trim_end_matches(".rs");
                filters.iter().any(|f| f == stem || f == *file)
            })
            .collect()
    };

    build_functions.par_iter().for_each(|(build_fn, file)| {
        println!("Parsing {}", file);

        let raw_code = build_fn().to_string();

        let header = "/* This file is generated. Do not edit manually. */\n";

        let final_code = format_code(&raw_code).map_or_else(
            |_| format!("{header}{raw_code}"),
            |formatted| format!("{header}{formatted}"),
        );

        write_generated_file(&final_code, file);
    });
    println!("Done")
}

/// Converts a slice of strings into a `TokenStream` of PascalCase enum variants.
///
/// # Arguments
/// - `array` – Slice of raw name strings to convert into variant identifiers.
#[must_use]
pub fn array_to_tokenstream(array: &[String]) -> TokenStream {
    let variants = array.iter().map(|item| {
        let name = format_ident!("{}", item.to_pascal_case());
        quote! { #name, }
    });

    quote! {
        #(#variants)*
    }
}

/// Writes generated source code to a file in [`OUT_DIR`], skipping the write if the
/// content is unchanged.
///
/// # Arguments
/// - `new_code` – The formatted source code string to write.
/// - `out_file` – The filename (relative to [`OUT_DIR`]) to write into.
pub fn write_generated_file(new_code: &str, out_file: &str) {
    let path = Path::new(OUT_DIR).join(out_file);

    if path.exists()
        && let Ok(existing_code) = fs::read_to_string(&path)
        && existing_code == new_code
    {
        return;
    }

    fs::write(&path, new_code)
        .unwrap_or_else(|_| panic!("Failed to write to file: {}", path.display()));
}

/// Error returned when `rustfmt` is unavailable or fails to format code.
pub struct RustFmtError;

/// Formats a Rust source string by piping it through `rustfmt`.
///
/// # Arguments
/// - `unformatted_code` – Raw Rust source code to format.
///
/// # Returns
/// The formatted source string, or `Err(RustFmtError)` if `rustfmt` is not available
/// or formatting fails.
pub fn format_code(unformatted_code: &str) -> Result<String, RustFmtError> {
    let child_result = Command::new("rustfmt")
        .stdin(Stdio::piped())
        .stdout(Stdio::piped())
        .stderr(Stdio::null())
        .spawn();

    let Ok(mut child) = child_result else {
        return Err(RustFmtError);
    };

    // Write the code to rustfmt's stdin
    if let Some(mut stdin) = child.stdin.take()
        && stdin.write_all(unformatted_code.as_bytes()).is_err()
    {
        return Err(RustFmtError);
    }

    match child.wait_with_output() {
        Ok(output) if output.status.success() => {
            String::from_utf8(output.stdout).map_err(|_| RustFmtError)
        }
        _ => Err(RustFmtError),
    }
}
