//! Channels target — emits `channels.ts` (typed message envelopes + channel
//! registry) and `channels.hooks.tsx` (`useXChannel` React hooks) from the
//! `x-mizan-channels` extension. Django-only feature; the FastAPI backend's
//! IR carries an empty channels list and this target emits nothing.

use std::path::PathBuf;

use askama::Template;
use indexmap::IndexMap;

use crate::config::Config;
use crate::emit::CodegenTarget;
use crate::emit::EmittedFile;
use crate::ir::{JsonSchema, MizanChannel, MizanIR};


pub struct ChannelsTarget;


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

    fn emit(&self, ir: &MizanIR, _config: &Config) -> Vec<EmittedFile> {
        if ir.channels.is_empty() {
            return Vec::new();
        }

        let schemas_block = emit_channel_schemas(&ir.channels, &ir.components.schemas);

        let types_content = ChannelsTypes {
            channels: ir.channels.iter().map(ChannelView::from_ir).collect(),
            schemas_block,
        }.render().expect("channels.ts renders");

        let mut type_imports: Vec<String> = Vec::new();
        for ch in &ir.channels {
            if ch.has_params { if let Some(t) = &ch.params_type { type_imports.push(t.clone()); } }
            if ch.has_react_message { if let Some(t) = &ch.react_message_type { type_imports.push(t.clone()); } }
            if ch.has_django_message { if let Some(t) = &ch.django_message_type { type_imports.push(t.clone()); } }
        }

        let hooks_content = ChannelsHooks {
            channels: ir.channels.iter().map(ChannelView::from_ir).collect(),
            type_imports,
        }.render().expect("channels.hooks.tsx renders");

        vec![
            EmittedFile::new(PathBuf::from("channels.ts"), types_content),
            EmittedFile::new(PathBuf::from("channels.hooks.tsx"), hooks_content),
        ]
    }
}


#[derive(Template)]
#[template(path = "channels/channels.ts.j2", escape = "none")]
struct ChannelsTypes<'a> {
    channels: Vec<ChannelView<'a>>,
    schemas_block: String,
}


#[derive(Template)]
#[template(path = "channels/channels.hooks.tsx.j2", escape = "none")]
struct ChannelsHooks<'a> {
    channels: Vec<ChannelView<'a>>,
    type_imports: Vec<String>,
}


struct ChannelView<'a> {
    name: &'a str,
    pascal_name: &'a str,
    has_params: bool,
    has_react_message: bool,
    has_django_message: bool,
    params_type: String,
    react_message_type: String,
    django_message_type: String,
    params_type_or_record: String,
    react_msg_type_or_never: String,
    django_msg_type_or_never: String,
}


impl<'a> ChannelView<'a> {
    fn from_ir(ch: &'a MizanChannel) -> Self {
        let params_type = ch.params_type.clone().unwrap_or_default();
        let react_message_type = ch.react_message_type.clone().unwrap_or_default();
        let django_message_type = ch.django_message_type.clone().unwrap_or_default();

        Self {
            name: &ch.name,
            pascal_name: &ch.pascal_name,
            has_params: ch.has_params,
            has_react_message: ch.has_react_message,
            has_django_message: ch.has_django_message,
            params_type_or_record: if ch.has_params { params_type.clone() } else { "Record<string, never>".to_string() },
            react_msg_type_or_never: if ch.has_react_message { react_message_type.clone() } else { "never".to_string() },
            django_msg_type_or_never: if ch.has_django_message { django_message_type.clone() } else { "never".to_string() },
            params_type,
            react_message_type,
            django_message_type,
        }
    }
}


fn emit_channel_schemas(
    channels: &[MizanChannel],
    schemas: &IndexMap<String, JsonSchema>,
) -> String {
    let mut blocks: Vec<String> = Vec::new();
    for ch in channels {
        for ty in [&ch.params_type, &ch.react_message_type, &ch.django_message_type].iter().filter_map(|t| t.as_ref()) {
            if let Some(schema) = schemas.get(ty) {
                blocks.push(emit_schema_as_ts(ty, schema));
            }
        }
    }
    blocks.join("\n\n")
}


fn emit_schema_as_ts(name: &str, schema: &JsonSchema) -> String {
    if let Some(props) = &schema.properties {
        let required: std::collections::HashSet<&str> =
            schema.required.iter().map(String::as_str).collect();
        let fields = props.iter()
            .map(|(field_name, field_schema)| {
                let opt = if required.contains(field_name.as_str()) { "" } else { "?" };
                let ty = ts_type_expression(field_schema);
                format!("    {field_name}{opt}: {ty}")
            })
            .collect::<Vec<_>>()
            .join("\n");
        if fields.is_empty() {
            format!("export interface {name} {{}}")
        } else {
            format!("export interface {name} {{\n{fields}\n}}")
        }
    } else {
        format!("export type {name} = {}", ts_type_expression(schema))
    }
}


fn ts_type_expression(schema: &JsonSchema) -> String {
    if let Some(ref_name) = schema.ref_name() {
        return ref_name.to_string();
    }
    match schema.ty.as_deref() {
        Some("integer") | Some("number") => "number".to_string(),
        Some("boolean") => "boolean".to_string(),
        Some("string") => "string".to_string(),
        Some("array") => {
            let elem = ts_type_expression(schema.items.as_deref().unwrap_or(&JsonSchema::default()));
            format!("{elem}[]")
        }
        Some("object") => "Record<string, unknown>".to_string(),
        _ => "unknown".to_string(),
    }
}
