use std::path::PathBuf;

use anyhow::Result;
use clap::Parser;

use clang_rpc_gen::{generate, DbusBackend, GenerateOptions};

/// Generates a gRPC C++ service adapter from an annotated C++ header.
#[derive(Parser)]
#[command(name = "clang-rpc-gen")]
struct Cli {
    /// The annotated C++ header to parse.
    #[arg(long)]
    header: PathBuf,

    /// Compiler-independent semantic context generated by the build system.
    /// Contains one kind=value record per line; see parse_context.rs.
    #[arg(long)]
    parse_context: PathBuf,

    /// Where to write the generated (disposable) .proto schema.
    #[arg(long)]
    out_proto: PathBuf,

    /// Where to write the generated ServiceImpl header.
    #[arg(long)]
    out_adapter_hh: PathBuf,

    /// Where to write the generated ServiceImpl source.
    #[arg(long)]
    out_adapter_cc: PathBuf,

    /// Optional separate output schema for payload enums/messages. Must be
    /// supplied together with --proto-types-package.
    #[arg(long)]
    out_types_proto: Option<PathBuf>,

    /// Package for the separate payload-types schema. This controls their
    /// wire names and generated C++ namespace without changing the service
    /// wire name. Must be supplied together with --out-types-proto.
    #[arg(long)]
    proto_types_package: Option<String>,

    /// Optional namespace appended to the protobuf package for generated
    /// gRPC service/stub classes. Does not change the wire service name.
    #[arg(long)]
    grpc_services_namespace: Option<String>,

    /// Optional C++ namespace for generated ServiceImpl and DBus binding
    /// adapters. This does not affect their wire names.
    #[arg(long)]
    adapter_namespace: Option<String>,

    /// Literal text for the generated `#include "..."` of the annotated
    /// header. Defaults to the header's file name.
    #[arg(long)]
    header_include: Option<String>,

    /// A file supplying `@rpc` tags by fully-qualified name, for
    /// declarations in `--header` that can't carry an annotation comment of
    /// their own. See external_annotations.rs for the file format.
    #[arg(long)]
    annotations: Option<PathBuf>,

    /// Where to write the generated DBus binding header, if the interface
    /// carries `@rpc.dbus(interface="...")`. Must be given together with
    /// --out-dbus-cc. See backends/dbus/.
    #[arg(long)]
    out_dbus_hh: Option<PathBuf>,

    /// Where to write the generated DBus binding source. Must be given
    /// together with --out-dbus-hh.
    #[arg(long)]
    out_dbus_cc: Option<PathBuf>,

    /// Literal header included by the generated DBus source.
    #[arg(long)]
    dbus_header_include: Option<String>,

    /// Native implementation used for generated DBus bindings.
    #[arg(long, value_enum, default_value_t = DbusBackend::Qt)]
    dbus_backend: DbusBackend,
}

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

    let opts = GenerateOptions {
        header: cli.header,
        parse_context: cli.parse_context,
        out_proto: cli.out_proto,
        out_adapter_hh: cli.out_adapter_hh,
        out_adapter_cc: cli.out_adapter_cc,
        out_types_proto: cli.out_types_proto,
        proto_types_package: cli.proto_types_package,
        grpc_services_namespace: cli.grpc_services_namespace,
        adapter_namespace: cli.adapter_namespace,
        header_include: cli.header_include,
        external_annotations: cli.annotations,
        out_dbus_hh: cli.out_dbus_hh,
        out_dbus_cc: cli.out_dbus_cc,
        dbus_header_include: cli.dbus_header_include,
        dbus_backend: cli.dbus_backend,
    };

    let generated = generate(&opts)?;
    println!("generated {}", generated.proto.display());
    if let Some(p) = &generated.types_proto {
        println!("generated {}", p.display());
    }
    println!("generated {}", generated.adapter_hh.display());
    println!("generated {}", generated.adapter_cc.display());
    if let Some(p) = &generated.dbus_hh {
        println!("generated {}", p.display());
    }
    if let Some(p) = &generated.dbus_cc {
        println!("generated {}", p.display());
    }

    Ok(())
}
