//! `source2rosetta-gen` — the standalone generator. Reads the published monolith (`gamedata-<game>.json`
//! from `source2rosetta produce`) and renders it into any framework's gamedata format at a chosen confidence
//! tier.
//!
//! It touches only the `model` + `render` layers — no ELF reader, no ptrace, no disassembler — so a consumer
//! who "just wants the files" downloads one monolith + this small, rarely-changing binary and generates
//! whatever their framework needs locally, instead of every format being pre-baked into releases. It lives in
//! the `source2rosetta-core` crate (serde-only), so it stays genuinely lean.

use anyhow::{Context, Result, bail};
use clap::Parser;
use source2rosetta_core::{model, render};
use std::path::PathBuf;

#[derive(Parser)]
#[command(
    name = "source2rosetta-gen",
    about = "Render a source2rosetta monolith into a framework gamedata format"
)]
struct Cli {
    /// The monolith `gamedata-<game>.json` (for a GAMEDATA --format: cssharp/metamod/modsharp/swiftly/plugify/model).
    #[arg(long)]
    from: Option<PathBuf>,
    /// The typed `netvars-<game>.json` (for a SCHEMA --format: cs-sdk/netvars).
    #[arg(long)]
    netvars: Option<PathBuf>,
    /// Output format. GAMEDATA (needs --from): cssharp | metamod | modsharp | swiftly | plugify | model.
    /// SCHEMA (needs --netvars): cs-sdk (typed C# SDK) | netvars (flat offset map). cssharp = the
    /// `//`-bannered CS# combined file; metamod also covers SourceMod (the VDF `.games.txt`).
    #[arg(long, default_value = "cssharp")]
    format: String,
    /// Confidence tier for a gamedata format (cumulative): core | high_confidence | experimental. Defaults to
    /// `high_confidence` (core + the promoted names). Ignored by schema formats.
    #[arg(long, default_value = "high_confidence")]
    tier: String,
    /// Write here (default: stdout).
    #[arg(long)]
    out: Option<PathBuf>,
}

fn main() -> Result<()> {
    let cli = Cli::parse();
    let fmt = cli.format.as_str();

    let text = if render::SCHEMA_FORMAT_IDS.contains(&fmt) {
        // schema formats render the typed netvars (class -> field -> offset/type), not the gamedata monolith.
        let path = cli.netvars.as_ref().context(
            "a schema --format (cs-sdk | netvars) requires --netvars <netvars-<game>.json>",
        )?;
        let schema: model::Schema = serde_json::from_str(&std::fs::read_to_string(path)?)
            .with_context(|| format!("parse netvars json {}", path.display()))?;
        render::schema_by_id(fmt)
            .expect("known schema format")
            .render(&schema)
    } else {
        let path = cli
            .from
            .as_ref()
            .context("a gamedata --format requires --from <gamedata-<game>.json>")?;
        let tier = model::TierSelect::from_id(&cli.tier).with_context(|| {
            format!(
                "unknown --tier {:?} (want one of: {})",
                cli.tier,
                model::TIER_IDS.join(" | ")
            )
        })?;
        let mono: model::Monolith = serde_json::from_str(&std::fs::read_to_string(path)?)
            .with_context(|| format!("parse monolith json {}", path.display()))?;
        match fmt {
            // cssharp is the bannered combined file (guaranteed + extrapolated sections), not a flat map.
            "cssharp" => render::render_monolith_cssharp(&mono, tier),
            f @ ("metamod" | "modsharp" | "swiftly" | "plugify" | "model") => render::by_id(f)
                .expect("known flat format")
                .render(&mono.select(tier)),
            other => bail!(
                "unknown --format {other:?} (gamedata: cssharp|metamod|modsharp|swiftly|plugify|model; \
                 schema: cs-sdk|netvars)"
            ),
        }
    };

    match cli.out {
        Some(p) => std::fs::write(&p, text).with_context(|| format!("write {}", p.display()))?,
        None => println!("{text}"),
    }
    Ok(())
}
