//! React target — Stage 2 emit on top of Stage 1. Wraps each registered
//! context in a React Provider so kernel subscription happens once per
//! provider mount; consumer hooks read from React Context.
//!
//! Output shape lives at `templates/react/react.tsx.j2`.

use std::path::PathBuf;

use askama::Template;

use crate::config::Config;
use crate::emit::CodegenTarget;
use crate::emit::EmittedFile;
use crate::emit::casing::pascal_case;
use crate::ir::{IsContext, MizanFunction, MizanIR};


pub struct ReactAdapter;


impl CodegenTarget for ReactAdapter {
    fn name(&self) -> &'static str { "react" }

    fn emit(&self, ir: &MizanIR, _config: &Config) -> Vec<EmittedFile> {
        let content = build_template(ir).render().expect("react template renders");
        vec![EmittedFile::new(PathBuf::from("react.tsx"), content)]
    }
}


#[derive(Template)]
#[template(path = "react/react.tsx.j2", escape = "none")]
struct ReactTemplate<'a> {
    has_global: bool,
    stage1_imports: Vec<String>,
    global_fns: Vec<HookRender<'a>>,
    named_contexts: Vec<CtxRender<'a>>,
    calls: Vec<CallRender>,
}


struct HookRender<'a> {
    pascal: String,
    output_type: &'a str,
    name: &'a str,
}


struct CtxRender<'a> {
    pascal: String,
    name: &'a str,
    has_params: bool,
    fns: Vec<HookRender<'a>>,
}


struct CallRender {
    pascal: String,
    has_input: bool,
}


fn dedupe_preserving_order(items: impl IntoIterator<Item = String>) -> Vec<String> {
    let mut seen = std::collections::HashSet::new();
    items.into_iter().filter(|s| seen.insert(s.clone())).collect()
}


fn build_template(ir: &MizanIR) -> ReactTemplate<'_> {
    let has_global = ir.contexts.contains_key("global");

    let global_fns: Vec<HookRender> = ir.functions.iter()
        .filter(|f| f.is_context.as_str() == Some("global"))
        .map(|f| HookRender {
            pascal: pascal_case(&f.camel_name),
            output_type: &f.output_type,
            name: &f.name,
        })
        .collect();

    let named_contexts: Vec<CtxRender> = ir.contexts.iter()
        .filter(|(n, _)| n.as_str() != "global")
        .map(|(ctx_name, ctx_meta)| {
            let ctx_fns: Vec<HookRender> = ir.functions.iter()
                .filter(|f| f.is_context.as_str() == Some(ctx_name.as_str()))
                .map(|f| HookRender {
                    pascal: pascal_case(&f.camel_name),
                    output_type: &f.output_type,
                    name: &f.name,
                })
                .collect();
            CtxRender {
                pascal: pascal_case(ctx_name),
                name: ctx_name,
                has_params: !ctx_meta.params.is_empty(),
                fns: ctx_fns,
            }
        })
        .collect();

    let mutations: Vec<&MizanFunction> = ir.functions.iter()
        .filter(|f| matches!(f.is_context, IsContext::No) && !f.is_form && !f.affects.is_empty())
        .collect();
    let plain_fns: Vec<&MizanFunction> = ir.functions.iter()
        .filter(|f| matches!(f.is_context, IsContext::No) && !f.is_form && f.affects.is_empty())
        .collect();

    let calls: Vec<CallRender> = mutations.iter().chain(plain_fns.iter())
        .map(|f| CallRender {
            pascal: pascal_case(&f.camel_name),
            has_input: f.has_input,
        })
        .collect();

    let mut stage1: Vec<String> = Vec::new();
    for ctx_name in ir.contexts.keys() {
        let p = pascal_case(ctx_name);
        stage1.push(format!("fetch{p}Context"));
        stage1.push(format!("type {p}ContextData"));
        stage1.push(format!("type {p}ContextParams"));
    }
    for fn_meta in mutations.iter().chain(plain_fns.iter()) {
        stage1.push(format!("call{}", pascal_case(&fn_meta.camel_name)));
    }
    let context_fns: Vec<&MizanFunction> = ir.functions.iter()
        .filter(|f| !matches!(f.is_context, IsContext::No))
        .collect();
    let output_types = dedupe_preserving_order(
        context_fns.iter().map(|f| f.output_type.clone()),
    );
    for t in output_types {
        stage1.push(format!("type {t}"));
    }

    ReactTemplate {
        has_global,
        stage1_imports: stage1,
        global_fns,
        named_contexts,
        calls,
    }
}
