// Copyright 2024 Rigetti Computing
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

//! Build-time utilities for generating, post-processing, and committing protobuf
//! code produced by [`prost-build`](super::prost_build),
//! [`tonic-build`](super::tonic_build), and [`pbjson-build`](super::pbjson_build).
//!
//! # Typical `build.rs` pattern
//!
//! ```rust,no_run
//! use std::{env, path::PathBuf};
//! use qcs_dependencies_client::codegen_grpc;
//!
//! fn main() {
//!     let out_dir = env::var("OUT_DIR").unwrap();
//!     let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("proto");
//!     let proto_files = vec![root.join("myservice/service.proto")];
//!
//!     let descriptor_path = PathBuf::from(&out_dir).join("proto_descriptor.bin");
//!
//!     // Pre-configured Config: extern_path, prost_path, compile_well_known_types.
//!     let mut config = codegen_grpc::qcs_prost_config();
//!     config.file_descriptor_set_path(&descriptor_path);
//!
//!     codegen_grpc::tonic_prost_build::configure()
//!         .compile_with_config(config, &proto_files, &[&root])
//!         .expect("failed to compile protos");
//!
//!     let descriptor_set = std::fs::read(&descriptor_path).unwrap();
//!     codegen_grpc::pbjson_build::Builder::new()
//!         .register_descriptors(&descriptor_set).unwrap()
//!         .build(&[".myservice"]).unwrap();
//!
//!     // Copy OUT_DIR/*.rs → src/gen/ with license header + path rewrites.
//!     let gen_dir = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src/gen");
//!     codegen_grpc::copy_generated_to_src(&out_dir, &gen_dir, Some(include_str!("../LICENSE_HEADER")));
//! }
//! ```
//!
//! # Path-rewriting strategy
//!
//! Because `prost`, `tonic`, `tonic_prost`, and `pbjson` are not direct
//! dependencies of consuming crates (they come through `qcs_dependencies_client`),
//! generated code needs two adjustments:
//!
//! - **prost** — handled at generation time via
//!   [`prost_build::Config::prost_path`](super::prost_build::Config::prost_path);
//!   [`qcs_prost_config`] sets this automatically.
//! - **tonic / tonic_prost / pbjson** — handled at copy time by
//!   [`fix_generated_paths`], which is called inside [`copy_generated_to_src`].

use std::{fs::OpenOptions, io::Write, path::Path};

/// Creates a [`prost_build::Config`] pre-configured to route all generated paths
/// through `qcs_dependencies_client`:
///
/// - `compile_well_known_types()` — replaces `prost-types` well-known types.
/// - `extern_path(".google.protobuf", "qcs_dependencies_client::pbjson_types")` —
///   maps protobuf well-known types to `pbjson-types`.
/// - `prost_path("::qcs_dependencies_client::prost")` — routes all prost
///   runtime paths in generated `impl` bodies through `qcs_dependencies_client`,
///   so consuming crates don't need a direct `prost` dependency.
///
/// Callers should add `file_descriptor_set_path`, `type_attribute`, etc. as needed.
pub fn qcs_prost_config() -> super::prost_build::Config {
    let mut config = super::prost_build::Config::new();
    config
        .compile_well_known_types()
        .extern_path(".google.protobuf", "qcs_dependencies_client::pbjson_types")
        .prost_path("::qcs_dependencies_client::prost");
    config
}

/// Copies every `.rs` file from `out_dir` to `dest_dir`.
///
/// Each file is written as follows:
/// 1. If `header` is `Some(text)`, `text` is written first (with a trailing
///    newline).
/// 2. The file content is processed by [`fix_generated_paths`] and written.
///
/// This is the standard way to commit generated protobuf code from `OUT_DIR`
/// into a `src/gen/` directory in source control.
///
/// # Panics
///
/// Panics on any I/O error (missing `out_dir`, unreadable files, etc.) — the
/// same behaviour as the original hand-rolled copy loops in each crate's
/// `build.rs`.
pub fn copy_generated_to_src(
    out_dir: impl AsRef<Path>,
    dest_dir: impl AsRef<Path>,
    header: Option<&str>,
) {
    let out_dir = out_dir.as_ref();
    let dest_dir = dest_dir.as_ref();

    for entry in std::fs::read_dir(out_dir)
        .unwrap_or_else(|e| panic!("cannot read OUT_DIR {}: {e}", out_dir.display()))
    {
        let src_path = entry
            .unwrap_or_else(|e| panic!("bad entry in OUT_DIR: {e}"))
            .path();

        if !src_path.to_string_lossy().ends_with(".rs") {
            continue;
        }

        let dest = dest_dir.join(
            src_path
                .file_name()
                .expect("OUT_DIR entry has no file name"),
        );

        let mut file = OpenOptions::new()
            .write(true)
            .create(true)
            .truncate(true)
            .open(&dest)
            .unwrap_or_else(|e| panic!("cannot open {} for writing: {e}", dest.display()));

        if let Some(h) = header {
            writeln!(file, "{h}").expect("failed to write header");
        }

        let content = std::fs::read_to_string(&src_path)
            .unwrap_or_else(|e| panic!("cannot read {}: {e}", src_path.display()));
        writeln!(file, "{}", fix_generated_paths(content))
            .expect("failed to write generated content");
    }
}

/// Post-processes a generated `.rs` file so it compiles when `tonic`,
/// `tonic_prost`, and `pbjson` are accessed through `qcs_dependencies_client`.
///
/// **prost paths are not rewritten here** — they are handled at generation time
/// via [`prost_build::Config::prost_path`](super::prost_build::Config::prost_path)
/// (set by [`qcs_prost_config`]).
pub fn fix_generated_paths(content: String) -> String {
    content
        // tonic: use statements and inline path references (single pass, no re-scan)
        .replace("tonic::", "qcs_dependencies_client::tonic::")
        // tonic_prost: codec references emitted by tonic-prost-build
        .replace("tonic_prost::", "qcs_dependencies_client::tonic_prost::")
        // pbjson: serde helper paths in pbjson-build output
        .replace(
            "pbjson::private::",
            "qcs_dependencies_client::pbjson::private::",
        )
}
