//! `wrangler.toml` generation per Worker (v0.8 §4.4 / §4.5).
//!
//! Each context becomes a Cloudflare Worker with its own wrangler config.
//! Service Bindings are declared for every consumed context. Durable
//! Object bindings + migrations are declared for every agent.

use std::fmt::Write as _;

use crate::project::{UnitTable, worker_dir_name};

/// Compile-time pinned compatibility date. Cloudflare uses this to lock
/// Workers runtime behaviour. Bump cautiously when changing the runtime
/// dependencies.
const COMPATIBILITY_DATE: &str = "2024-11-01";

pub fn emit_wrangler_toml(
    context: &str,
    table: &UnitTable,
    consumes: &[String],
    // v0.19 (C1): this Worker's closure reaches bynk.cloudflare — declare the
    // KV namespace binding (the `id` is a deploy-time placeholder).
    needs_kv: bool,
) -> String {
    let name = worker_dir_name(context);
    let mut out = String::new();
    let _ = writeln!(out, "# Generated by bynkc — do not edit by hand.");
    let _ = writeln!(out, "name = \"{name}\"");
    let _ = writeln!(out, "main = \"index.ts\"");
    let _ = writeln!(out, "compatibility_date = \"{COMPATIBILITY_DATE}\"");
    writeln!(out).unwrap();

    let mut sorted_consumes: Vec<&String> = consumes.iter().collect();
    sorted_consumes.sort();
    for target in &sorted_consumes {
        let binding = consumed_binding_name(target);
        let service = worker_dir_name(target);
        let _ = writeln!(out, "[[services]]");
        let _ = writeln!(out, "binding = \"{binding}\"");
        let _ = writeln!(out, "service = \"{service}\"");
        writeln!(out).unwrap();
    }

    if needs_kv {
        let _ = writeln!(out, "[[kv_namespaces]]");
        let _ = writeln!(out, "binding = \"{}\"", crate::firstparty::KV_BINDING_NAME);
        let _ = writeln!(out, "id = \"<KV_NAMESPACE_ID>\" # set at deploy time");
        writeln!(out).unwrap();
    }

    // Agents → Durable Object bindings + migrations.
    let mut agent_names: Vec<&String> = table.agents.keys().collect();
    agent_names.sort();
    for agent_name in &agent_names {
        let binding = agent_binding_name(agent_name);
        let _ = writeln!(out, "[[durable_objects.bindings]]");
        let _ = writeln!(out, "name = \"{binding}\"");
        let _ = writeln!(out, "class_name = \"{agent_name}\"");
        writeln!(out).unwrap();
    }
    if !agent_names.is_empty() {
        let _ = writeln!(out, "[[migrations]]");
        let _ = writeln!(out, "tag = \"v1\"");
        let classes: Vec<String> = agent_names.iter().map(|n| format!("\"{n}\"")).collect();
        let _ = writeln!(out, "new_classes = [{}]", classes.join(", "));
        writeln!(out).unwrap();
    }

    // v0.10a: cron triggers. Cloudflare uses a single `[triggers]` table with a
    // `crons` array aggregating every `on cron` schedule in the context.
    let mut crons: Vec<&String> = Vec::new();
    for service in table.services.values() {
        for handler in &service.handlers {
            if let crate::ast::HandlerKind::Cron { expr } = &handler.kind {
                crons.push(expr);
            }
        }
    }
    crons.sort();
    crons.dedup();
    if !crons.is_empty() {
        let quoted: Vec<String> = crons.iter().map(|e| format!("\"{e}\"")).collect();
        let _ = writeln!(out, "[triggers]");
        let _ = writeln!(out, "crons = [{}]", quoted.join(", "));
        writeln!(out).unwrap();
    }

    // v0.10b: queue consumers. Each `on queue "name"` becomes a
    // `[[queues.consumers]]` binding.
    let mut queues: Vec<&String> = Vec::new();
    for service in table.services.values() {
        // v0.44: one queue binding per service, on the `from queue("name")` header.
        if let crate::ast::ServiceProtocol::Queue { name } = &service.protocol {
            queues.push(name);
        }
    }
    queues.sort();
    queues.dedup();
    for name in &queues {
        let _ = writeln!(out, "[[queues.consumers]]");
        let _ = writeln!(out, "queue = \"{name}\"");
        let _ = writeln!(out, "max_batch_size = 10");
        writeln!(out).unwrap();
    }

    out
}

/// Service Binding identifier for a consumed context: uppercase with
/// underscores. `commerce.payment` → `COMMERCE_PAYMENT`.
pub fn consumed_binding_name(target: &str) -> String {
    target.replace('.', "_").to_uppercase()
}

/// Durable Object binding identifier for an agent class. We use the
/// class name in screaming snake case so handlers can grab it by a
/// predictable name (`OrderEntity` → `ORDER_ENTITY`).
pub fn agent_binding_name(class_name: &str) -> String {
    let mut out = String::new();
    for (i, ch) in class_name.chars().enumerate() {
        if i > 0 && ch.is_uppercase() {
            out.push('_');
        }
        out.push(ch.to_ascii_uppercase());
    }
    out
}
