//! generate_image AI 工具声明式注册(调 provider 图像生成端点 + 下载落地)。
//!
//! 工具职责:调 provider 的 OpenAI 兼容 `/v1/images/generations` 端点生成图片,
//! 自动下载到工作区 output_path。默认 model=sensenova-u1-fast(SenseNova U1 信息图生成),
//! 也支持 DALL-E 等 OpenAI 兼容图像端点。
//!
//! 风险:High(付费 API 按张计费 + 写磁盘 + 外发网络)。
//!
//! 与 download_file 的分工:download_file 是「已知 URL → 落盘」,generate_image 是
//! 「调 provider 生成 + 自动落盘」(封装 provider 凭证 + 端点拼接 + 响应解析 + 图片下载全链路)。
//! 图像生成模型不是 chat 模型,走 /v1/images/generations 非 chat completions,
//! 故不能经主对话链路调用,必须用本工具显式触发。
//!
//! handler 在 commands/ai/generate_image.rs::execute_generate_image,声明式注册收敛样板。
//! 闭包捕获 db:Arc<Database>(选 provider + 解析凭证)+ allowed_dirs:Arc<RwLock<AllowedDirs>>
//! (output_path 白名单校验 + 默认路径锚定)。
//!
//! ## 捕获变量说明(declare_tool! 单捕获限制)
//!
//! declare_tool! 宏只支持单捕获变量(见 crates/df-ai/src/ai_tools_decl.rs),本工具需 db +
//! allowed_dirs 两个,故包进 tuple Arc `(Arc<Database>, Arc<RwLock<AllowedDirs>>)` 在闭包内解构。
//! 这是宏单捕获限制的标准绕法(零架构改动:不改宏、不改 process_tool_calls 签名)。
//!
//! ## 注册位置说明
//!
//! generate_image 涉付费 API(网络)+ 写文件(落盘 output_path),与 download_file 同源
//! (均持 allowed_dirs),归 register_http_tools 同层(register_generate_image_tool 子函数,
//! 在 register_http_tools 之后调用)。

use std::sync::Arc;

use df_ai::ai_tools::{AiToolRegistry, RiskLevel};
use df_ai::declare_tool;
use tokio::sync::RwLock;

use crate::state::AllowedDirs;
use df_storage::db::Database;

/// generate_image 捕获的 (db, allowed_dirs) tuple 类型(declare_tool! 单捕获绕法)。
type GenerateImageCtx = (Arc<Database>, Arc<RwLock<AllowedDirs>>);

/// 注册 generate_image 工具到 `$registry`。
///
/// - name/desc/schema 面向 LLM 的工具说明
/// - risk=High(付费 API + 写磁盘 + 外发网络)
/// - handler 捕获 (db, allowed_dirs) tuple Arc,解构后转调 generate_image.rs::execute_generate_image
///   (provider 选择 + 凭证解析 + 端点拼接 + SSRF 防护下载 + 原子写全在那)
pub fn register(
    registry: &mut AiToolRegistry,
    db: &Arc<Database>,
    allowed_dirs: &Arc<RwLock<AllowedDirs>>,
) {
    // 捕获 tuple:declare_tool! 单捕获限制的绕法。
    let ctx: GenerateImageCtx = (db.clone(), allowed_dirs.clone());

    // schema:prompt(必填)+ model(默认 sensenova-u1-fast)+ size(可选)+ n(默认 1)
    //       + provider_id(可选)+ output_path(可选)。
    let schema = {
        let mut props = serde_json::Map::new();
        props.insert("prompt".into(), serde_json::json!({ "type": "string", "description": "图片描述(中英文均可),越具体生成质量越高。例:'一只戴墨镜的橘猫坐在键盘上,赛博朋克风格,霓虹光'" }));
        props.insert("model".into(), serde_json::json!({ "type": "string", "description": "图像生成模型名。默认 sensenova-u1-fast(SenseNova U1 信息图生成,走 /v1/images/generations)。也支持 dall-e-3 等 OpenAI 兼容图像模型。不传用默认", "default": "sensenova-u1-fast" }));
        props.insert("size".into(), serde_json::json!({ "type": "string", "description": "可选图片尺寸字符串,不传则让 provider 用默认。SenseNova U1 常用 2752x1536 / 1536x2752(横/竖海报),DALL-E 常用 1024x1024" }));
        props.insert("n".into(), serde_json::json!({ "type": "integer", "description": "生成数量(默认 1,clamp [1,4])。多张时取首张 url/b64 落地", "minimum": 1, "maximum": 4, "default": 1 }));
        props.insert("provider_id".into(), serde_json::json!({ "type": "string", "description": "可选,指定 provider(多 provider 时);不传则按 model 名自动匹配厂商 host(sensenova→SenseNova,dall-e→OpenAI,google/imagen→Google),匹配不到取首个 openai_compat provider" }));
        props.insert("output_path".into(), serde_json::json!({ "type": "string", "description": "可选落盘路径(相对路径锚定到授权项目根,绝对路径须在授权目录内)。不传则默认 {首个持久授权目录}/generated_images/{model}-{时间戳}.png。父目录不存在自动创建,覆盖既有同名文件。注意:这是图像生成模型(非对话模型),通过本工具调用而非主对话链路" }));
        serde_json::json!({
            "type": "object",
            "properties": props,
            "required": ["prompt"],
        })
    };
    declare_tool!(
        registry,
        ctx: GenerateImageCtx,
        "generate_image",
        "调用图像生成模型(默认 SenseNova U1 Fast 信息图生成,也支持 DALL-E 等 OpenAI 兼容图像端点)生成图片并下载到工作区。参数:prompt(图片描述,必填),model(默认 sensenova-u1-fast),size(可选,SenseNova U1 常用 2752x1536/1536x2752,DALL-E 常用 1024x1024),n(默认 1,clamp [1,4]),provider_id(可选,多 provider 时指定,否则按 model 名自动匹配厂商),output_path(可选,默认 generated_images/{model}-{时间戳}.png)。注意:本工具调用图像生成模型(非对话模型),走 /v1/images/generations 端点。返回 {path, url(或 null), model, provider_id, bytes_written, elapsed_ms}。风险:付费 API 按张计费 + 写磁盘",
        RiskLevel::High,
        schema: schema,
        args => {
            // 解构 tuple 捕获:db 选 provider + 解析凭证,allowed_dirs 解析 output_path 白名单
            let (db, allowed_dirs) = ctx;
            crate::commands::ai::generate_image::execute_generate_image(args, &db, &allowed_dirs).await
        }
    );
}
