pub mod builtins;
pub mod c;
pub mod cmake_lists;
pub mod common;
pub mod docs_site;
pub mod dotnet;
pub mod expr_walk;
pub mod func_api_xml;
pub mod func_list;
pub mod java;
pub mod java_enums;
pub mod java_shipped;
pub mod makefile_am;
pub mod retcode;
pub mod rust_abstract;
pub mod rust_doc;
pub mod rust_lang;
pub mod stmt_walk;
pub mod ta_abstract_c;
pub mod ta_defs;

use crate::helper_registry::HelperRegistry;
use crate::ir::{EnumDef, FuncDef};
use crate::registry::Registry;
use crate::server_gen;
use std::collections::HashMap;
use std::path::Path;

/// A per-indicator code-generation backend for one target language.
///
/// Each backend knows three things the generate/clean pipelines need: how to
/// render a single indicator, where its per-indicator file lives, and how to
/// recognise its own generated files for stale-cleanup. Routing those through a
/// registry of `LanguageBackend` implementations means a new language is
/// registered in exactly one place ([`all`]) instead of being threaded through
/// several `match backend { ... }` sites.
pub trait LanguageBackend {
    /// Short backend identifier used on the CLI (`c`, `rust`, `java`, `dotnet`).
    fn name(&self) -> &'static str;

    /// Render the per-indicator source for `func`.
    fn generate(
        &self,
        func: &FuncDef,
        enums: &HashMap<String, EnumDef>,
        registry: &Registry,
        helpers: &HelperRegistry,
    ) -> String;

    /// Output directory for per-indicator files, relative to `ta_codegen/output/`.
    fn out_subdir(&self) -> &'static str;

    /// File name for the per-indicator file of `func` (e.g. `ta_SMA.c`).
    fn file_name(&self, func: &FuncDef) -> String;

    /// `(prefix, suffix)` identifying this backend's generated per-indicator
    /// files, used to remove stale artifacts before regenerating.
    fn clean_glob(&self) -> (&'static str, &'static str);

    /// Hand-written / scaffolding files in [`out_subdir`](Self::out_subdir) that
    /// must be preserved by stale-cleanup.
    fn clean_keep(&self) -> &'static [&'static str] {
        &[]
    }

    /// Absolute directory the per-indicator library files are written to.
    /// Defaults to `out_base/<out_subdir>`. The C backend overrides this to emit
    /// the shipped C library **directly into `src/ta_func`** (canonical cutover
    /// option B) instead of the `ta_codegen/output/c` staging tree.
    fn lib_output_dir(&self, out_base: &Path) -> std::path::PathBuf {
        out_base.join(self.out_subdir())
    }

    /// Generate and write this backend's JSON-RPC test server source under
    /// `out_base` (`ta_codegen/output/`).
    fn generate_server(&self, funcs: &[FuncDef], out_base: &Path);
}

/// The C reference-compatible backend.
pub struct CBackend;
impl LanguageBackend for CBackend {
    fn name(&self) -> &'static str {
        "c"
    }
    fn generate(
        &self,
        func: &FuncDef,
        enums: &HashMap<String, EnumDef>,
        registry: &Registry,
        helpers: &HelperRegistry,
    ) -> String {
        c::generate(func, enums, registry, helpers)
    }
    fn out_subdir(&self) -> &'static str {
        "c/ta_func"
    }
    fn file_name(&self, func: &FuncDef) -> String {
        format!("ta_{}.c", func.name)
    }
    fn clean_glob(&self) -> (&'static str, &'static str) {
        ("ta_", ".c")
    }
    /// `ta_utility.c` is a hand-written helper that lives in `src/ta_func`
    /// alongside the generated indicators — never delete it during cleanup.
    fn clean_keep(&self) -> &'static [&'static str] {
        &["ta_utility.c"]
    }
    /// Shipped C library is generated in place under `src/ta_func` (option B):
    /// `out_base` is `<root>/ta_codegen/output`, so `../../src/ta_func` = `<root>/src/ta_func`.
    fn lib_output_dir(&self, out_base: &Path) -> std::path::PathBuf {
        out_base
            .parent()
            .and_then(Path::parent)
            .expect("out_base is <root>/ta_codegen/output")
            .join("src/ta_func")
    }
    fn generate_server(&self, funcs: &[FuncDef], out_base: &Path) {
        // ta_func_unguarded.h is generated by `generate()`, not here.
        let dir = out_base.join("c");
        std::fs::create_dir_all(&dir).unwrap();
        let output = server_gen::generate_c_server(funcs);
        let path = dir.join("ta_codegen_serve.c");
        std::fs::write(&path, &output).unwrap();
        println!("  C server -> {}", path.display());
    }
}

/// The Rust crate backend.
pub struct RustBackend;
impl LanguageBackend for RustBackend {
    fn name(&self) -> &'static str {
        "rust"
    }
    fn generate(
        &self,
        func: &FuncDef,
        enums: &HashMap<String, EnumDef>,
        registry: &Registry,
        helpers: &HelperRegistry,
    ) -> String {
        rust_lang::generate(func, enums, registry, helpers)
    }
    fn out_subdir(&self) -> &'static str {
        "rust/src/ta_func"
    }
    fn file_name(&self, func: &FuncDef) -> String {
        format!("{}.rs", func.name.to_lowercase())
    }
    fn clean_glob(&self) -> (&'static str, &'static str) {
        ("", ".rs")
    }
    fn clean_keep(&self) -> &'static [&'static str] {
        &["types.rs", "mod.rs"]
    }
    fn generate_server(&self, funcs: &[FuncDef], out_base: &Path) {
        let dir = out_base.join("rust/src/bin");
        std::fs::create_dir_all(&dir).unwrap();
        let output = server_gen::generate_rust_server(funcs);
        let path = dir.join("ta_codegen_serve.rs");
        std::fs::write(&path, &output).unwrap();
        println!("  Rust server -> {}", path.display());
    }
}

/// The Java `Core` bindings backend.
pub struct JavaBackend;
impl LanguageBackend for JavaBackend {
    fn name(&self) -> &'static str {
        "java"
    }
    fn generate(
        &self,
        func: &FuncDef,
        enums: &HashMap<String, EnumDef>,
        registry: &Registry,
        helpers: &HelperRegistry,
    ) -> String {
        java::generate(func, enums, registry, helpers)
    }
    fn out_subdir(&self) -> &'static str {
        "java"
    }
    fn file_name(&self, func: &FuncDef) -> String {
        format!("Core_{}.java", func.name)
    }
    fn clean_glob(&self) -> (&'static str, &'static str) {
        ("Core_", ".java")
    }
    fn generate_server(&self, funcs: &[FuncDef], out_base: &Path) {
        let dir = out_base.join("java");
        std::fs::create_dir_all(&dir).unwrap();
        let template = server_gen::generate_java_server(funcs);
        let output = server_gen::inline_java_core_methods(&template, &dir, funcs);
        let path = dir.join("TaCodegenServe.java");
        std::fs::write(&path, &output).unwrap();
        println!("  Java server -> {}", path.display());
    }
}

/// The .NET P/Invoke wrappers backend.
pub struct DotNetBackend;
impl LanguageBackend for DotNetBackend {
    fn name(&self) -> &'static str {
        "dotnet"
    }
    fn generate(
        &self,
        func: &FuncDef,
        enums: &HashMap<String, EnumDef>,
        registry: &Registry,
        helpers: &HelperRegistry,
    ) -> String {
        dotnet::generate(func, enums, registry, helpers)
    }
    fn out_subdir(&self) -> &'static str {
        "dotnet"
    }
    fn file_name(&self, func: &FuncDef) -> String {
        format!("Core_{}.h", func.name)
    }
    fn clean_glob(&self) -> (&'static str, &'static str) {
        ("Core_", ".h")
    }
    fn generate_server(&self, funcs: &[FuncDef], out_base: &Path) {
        let output = server_gen::generate_dotnet_server(funcs);
        let dir = out_base.join("dotnet");
        std::fs::create_dir_all(&dir).unwrap();
        let path = dir.join("TaCodegenServe.cs");
        std::fs::write(&path, &output).unwrap();
        println!("  .NET server -> {}", path.display());
    }
}

/// All per-indicator backends, in canonical order. Registering a new language
/// is a one-line addition here.
pub fn all() -> Vec<Box<dyn LanguageBackend>> {
    vec![
        Box::new(CBackend),
        Box::new(RustBackend),
        Box::new(JavaBackend),
        Box::new(DotNetBackend),
    ]
}

/// The canonical backend names (`["c", "rust", "java", "dotnet"]`).
pub fn all_names() -> Vec<&'static str> {
    all().iter().map(|b| b.name()).collect()
}

/// Look up a backend by its CLI name.
pub fn get(name: &str) -> Option<Box<dyn LanguageBackend>> {
    all().into_iter().find(|b| b.name() == name)
}

/// Write `content` to `path` only if it differs from the current file contents.
/// Prints a one-line status message using `label` and `count`.
pub fn write_if_changed(path: &std::path::Path, content: &str, label: &str, count: usize) {
    let existing = std::fs::read_to_string(path).unwrap_or_default();
    if existing == content {
        println!("  {label} is up to date ({count} functions)");
    } else {
        std::fs::write(path, content).unwrap();
        println!("  {label} updated ({count} functions)");
    }
}

/// Like [`write_if_changed`] but without the status message — for high-volume
/// callers (e.g. the per-file `ta_abstract` writers) that report progress themselves.
pub fn write_if_changed_silent(path: &std::path::Path, content: &str) {
    let existing = std::fs::read_to_string(path).unwrap_or_default();
    if existing != content {
        std::fs::write(path, content).unwrap();
    }
}

/// The sorted `ta_<STEM>.c` source stems for the generated C library, shared by the
/// CMake (`LIB_SOURCES`) and autotools source-list generators so the two lists cannot
/// drift apart. Every function in `ta_codegen/input/`, upper-cased and sorted.
///
/// Returns `(sorted_stems, extras)`; `extras` is always empty (kept for the callers'
/// signature). The former un-ported `src/ta_func/` stubs (NVI/PVI) are dropped in the
/// canonical cutover — they are dead (absent from `include/` and the `ta_abstract`
/// tables, not in the 161-function set).
pub fn sorted_source_stems(funcs: &[FuncDef], _root: &Path) -> (Vec<String>, Vec<String>) {
    let mut names: Vec<String> = funcs.iter().map(|f| f.name.to_uppercase()).collect();
    names.sort();
    (names, Vec::new())
}
