//! DevFlow Tauri 入口 — 初始化 AppState 并注册全部 IPC 命令

mod commands;
mod state;

use tauri::{Emitter, Listener, Manager};

use state::AppState;

/// 用户本地时区日志时间戳格式化器。
/// tracing_subscriber 默认输出 UTC,用户在中国 UTC+8,日志与本地时间差 8 小时不便调试。
/// 使用 chrono::Local 输出含时区偏移的本地时间(如 "2026-07-17T15:30:00.123456+08:00")。
///
/// 注意:FormatTime::format_time 参数类型为 tracing_subscriber::fmt::format::Writer,
/// 非 std::fmt::Write。Writer 实现了 fmt::Write,可直接 write!。
struct LocalTimer;
impl tracing_subscriber::fmt::time::FormatTime for LocalTimer {
    fn format_time(&self, w: &mut tracing_subscriber::fmt::format::Writer<'_>) -> std::fmt::Result {
        write!(w, "{}", chrono::Local::now().format("%Y-%m-%dT%H:%M:%S%.6f%:z"))
    }
}

// Phase3 跨端隧道:TunnelClient trait(connect/send_raw_event/disconnect 方法)需在作用域内
use df_tunnel::TunnelClient;

#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
    // 初始化文件日志:追加到 %TEMP%/devflow-trace.log,RUST_LOG 控制级别(默认 info)
    let log_path = std::env::temp_dir().join("devflow-trace.log");
    let log_file = std::fs::OpenOptions::new()
        .create(true)
        .append(true)
        .open(&log_path)
        .expect("创建日志文件失败");
    let (non_blocking, _guard) = tracing_appender::non_blocking(log_file);
    tracing_subscriber::fmt()
        .with_timer(LocalTimer)
        .with_env_filter(
            tracing_subscriber::EnvFilter::try_from_default_env()
                .unwrap_or_else(|_| tracing_subscriber::EnvFilter::new("info"))
        )
        .with_writer(non_blocking)
        .with_ansi(false)
        .init();
    tracing::info!(
        path = %log_path.display(),
        "[startup] 日志已初始化"
    );

    tauri::Builder::default()
        .plugin(tauri_plugin_opener::init())
        .plugin(tauri_plugin_dialog::init())
        .plugin(tauri_plugin_window_state::Builder::default().build())
        .setup(|app| {
            // 数据库放在系统应用数据目录 <app_data_dir> 下（dev/release 拆分）：
            // Dev 模式 devflow-dev.db（可随意改动/清空），Build 模式 devflow.db（长期保留真实运行数据）
            let db_name = if cfg!(debug_assertions) { "devflow-dev.db" } else { "devflow.db" };
            let data_dir = app.path().app_data_dir()?;
            std::fs::create_dir_all(&data_dir)?;
            let db_path = data_dir.join(db_name);

            // 初始化全局状态（打开数据库 + 执行迁移），传入运行期确定的 data_dir
            let app_state = tauri::async_runtime::block_on(AppState::init(&db_path, data_dir.clone()))?;
            // FR-S1:启动一次性迁移 DB 明文 api_key → OS keyring(失败保留明文下次重试,非阻断)
            if let Err(e) = tauri::async_runtime::block_on(
                commands::ai::secret::migrate_secrets_to_keyring(&app_state.ai_providers),
            ) {
                tracing::warn!("[FR-S1] 启动密钥迁移失败(非阻断): {}", e);
            }
            // B-260616-01: L0 握手 — 提前 clone ai_session(Arc),因 manage() 会 move app_state
            let session_for_handshake = app_state.ai_session.clone();
            app.manage(app_state);

            // B-260616-01: L0 握手 — 监听前端就绪事件，清除 HMR/刷新导致的残留 generating 状态
            // 任务5: 3 秒防抖 —— 前端 HMR/快速连击会连发 ai-client-ready(实测 <1s 内多次),
            // 每次都走完整 握手(spawn + 锁 session + emit)造成事务事并行冲突 + emit 风暴。
            // 记录上次处理时间,3s 内重复事件跳过(只取首次,使能状态复位一次即可)。
            let last_handshake_at = std::sync::Arc::new(tokio::sync::Mutex::new(
                std::time::Instant::now()
                    .checked_sub(std::time::Duration::from_secs(3600))
                    .unwrap_or_else(std::time::Instant::now),
            ));
            let app_handle = app.handle().clone();
            app.listen("ai-client-ready", move |_event| {
                let session_arc = session_for_handshake.clone();
                let app_h = app_handle.clone();
                let last_at = last_handshake_at.clone();
                tauri::async_runtime::spawn(async move {
                    // 防抖检查:锁 last_at 读上次时间,3s 内跳过(仅记录日志,不做实际握手动作)。
                    {
                        let mut last = last_at.lock().await;
                        let elapsed = last.elapsed();
                        if elapsed < std::time::Duration::from_secs(3) {
                            tracing::info!(
                                "[L0-handshake] 跳过(距上次握手 {}ms < 3000ms 防抖)",
                                elapsed.as_millis()
                            );
                            return;
                        }
                        *last = std::time::Instant::now();
                    }
                    let mut session = session_arc.lock().await;
                    let app_state_ref = app_h.state::<AppState>();
                    // 职责分离(根治 BUG-2026-07-08 误杀活 loop):
                    //   生产环境前端 WebSocket 重连 / webview reload 会触发 ai-client-ready(非用户重发),
                    //   旧握手无脑强杀正在跑的 agentic loop(stop_flag + transition Idle + emit AiCompleted)
                    //   → AI 中断不回复 → 用户「莫名其妙停止」(实证 cf5f44ed:seq26 后 loop 等下次 LLM 被杀)。
                    //
                    //   三道防线已就绪,握手无需也不应再强杀 loop:
                    //   1. guard.rs GeneratingGuard RAII:new→Generating,reset→Idle(正常),Drop→Idle(panic 兜底)。
                    //      loop 自己复位 generating,握手强杀是多余且有害的旁路。
                    //   2. conv_state.rs can_accept_request 只 Idle|Error 可接 → generating 时 ai_chat_send
                    //      拒新消息 → 防「用户重发双 loop」(原 BUG-2026-07-08 的真实诉求)。
                    //   3. max_iterations 超时兜底 loop 卡死。
                    //
                    //   故握手职责收窄为「同步真实状态给前端」:读 conv_states 当前状态逐个 emit,
                    //   不改写 conv_states、不设 stop_flag、不收尾。loop 在跑则前端拿到 Generating,
                    //   loop 已退出(guard 已复位)则拿到 Idle —— 前端状态与后端一致即可。
                    let dirty_convs: Vec<String> = app_state_ref.conv_states.active_convs();
                    let was_generating = !dirty_convs.is_empty();
                    // BUG-260619-06 修复: clear 致冷启动 restore 重建审批丢失(restore 填充后 clear 无条件清空,
                    //   重启后待审批工具全丢)。改 retain 仅清非 recovered(本次会话/HMR 死 pending),
                    //   保留 restore 重建(recovered=true,audit.rs:331),对齐 switchConversation retain 保护意图。
                    // 阶段3a 单真相源合并:单表按 !recovered retain(kind 不区分,path 审批恢复恒无)。
                    session.pending_approvals.retain(|_, a| !a.recovered);
                    // 握手需推状态的 conv 集合:dirty conv(后端已知活跃)+ 当前 active_conv(用户正看的)。
                    // dirty_convs 已含活跃 conv;active_conv 若非活跃(Idling/Eror)也补推一次让前端同步。
                    let active_conv = session.active_conversation_id.clone();
                    let mut convs_to_sync: Vec<String> = dirty_convs.clone();
                    if let Some(ref cid) = active_conv {
                        if !convs_to_sync.iter().any(|c| c == cid) {
                            convs_to_sync.push(cid.clone());
                        }
                    }
                    drop(session);
                    // 逐个 conv 读 conv_states 真实状态推给前端(loop 在跑推 Generating/Compressed,
                    // 已退出推 Idle)。ConvStateStore.get 同步无锁,不竞争 session lock。
                    for cid in &convs_to_sync {
                        let real_state = app_state_ref.conv_states.get(cid);
                        let _ = app_h.emit(
                            "ai-chat-event",
                            commands::ai::AiChatEvent::AiConvStateChanged {
                                conv_state: real_state,
                                conversation_id: Some(cid.clone()),
                            },
                        );
                    }
                    tracing::info!(
                        "[L0-handshake] 前端重连握手完成, was_generating={}, dirty_convs={:?}",
                        was_generating,
                        dirty_convs
                    );
                });
            });

            // Phase3 跨端隧道集成:tunnel connect relay + subscriber 透传 ai_event_bus → miniapp
            // + on_command 下行路由(miniap 指令 → remote_bridge → Tauri command)。
            // spawn 后台 task:device_id/relay_url 解析(持久 KV,无则默认)→ subscriber 透传
            // → on_command 回调注册 → connect(失败非阻断,可后续手动重连)。
            let tunnel_app = app.handle().clone();
            tauri::async_runtime::spawn(async move {
                let state = tunnel_app.state::<AppState>();

                // device_id:KV 取,无则生成 UUID v4 落库(持久,relay 按 device_id 路由配对)。
                let device_id = match state.settings.get("device_id").await {
                    Ok(Some(id)) if !id.is_empty() => id,
                    _ => {
                        let id = uuid::Uuid::new_v4().to_string();
                        let _ = state.settings.set("device_id", &id).await;
                        id
                    }
                };
                // relay_url:KV 取,无则默认本地(localhost:8080,本地开发 df-relay 默认监听)。
                // settings value 契约模糊(JSON 序列化带引号或裸值并存,见 SettingsRepo get/set 不解析 JSON):
                // 统一解析去引号 —— serde_json::from_str 处理 JSON 字符串值,失败(裸值)则 trim 首尾引号兜底,
                // 确保拿到纯净 URL 供 connect_async(带引号 URL 解析失败致 connect 静默失败,relay 无 device 接入)。
                let relay_url = state
                    .settings
                    .get("relay_url")
                    .await
                    .ok()
                    .flatten()
                    .filter(|s| !s.is_empty())
                    .map(|raw| {
                        serde_json::from_str::<String>(&raw)
                            .unwrap_or_else(|_| raw.trim_matches('"').to_string())
                    })
                    .unwrap_or_else(|| "ws://localhost:8080/ws/device".to_string());
                // token:固定常量(三端同源,对齐 df-relay relay.rs:37 DEFAULT_TOKEN)
                let token = "devflow-relay-default-token".to_string();

                // subscriber task:subscribe ai_event_bus → tunnel.send_raw_event 透传 miniapp
                // 治本:先查 is_connected()(AtomicBool 无锁读),未连接时静默丢弃事件,
                //   避免每次 send_raw_event 都失败并记 WARN(旧实现用 suppress_until 降频,是治标)。
                let mut rx = state.ai_event_bus.subscribe();
                let tunnel_for_sub = state.tunnel.clone();
                tauri::async_runtime::spawn(async move {
                    tracing::info!("[tunnel-sub] subscriber task 启动,透传 ai_event_bus → relay");
                    let mut was_connected = false;
                    while let Ok(value) = rx.recv().await {
                        if !tunnel_for_sub.is_connected() {
                            if was_connected {
                                tracing::info!("[tunnel-sub] tunnel 已断开,暂停透传");
                                was_connected = false;
                            }
                            continue;
                        }
                        if !was_connected {
                            tracing::info!("[tunnel-sub] tunnel 已重连,恢复透传");
                            was_connected = true;
                        }
                        if let Err(e) = tunnel_for_sub.send_raw_event(value).await {
                            // 连接刚断(查询与发送间窗口),记一条 DEBUG 而非 WARN
                            tracing::debug!("[tunnel-sub] send_raw_event 失败(连接瞬断): {}", e);
                            was_connected = false;
                        }
                    }
                    tracing::info!("[tunnel-sub] subscriber task 退出(rx 关闭)");
                });

                // on_command 回调:miniapp 下行指令 → remote_bridge 路由 Tauri command。
                // CommandHandler = Arc<dyn Fn(Value) -> BoxFuture<'static, ()> + Send + Sync>
                // (tunnel.rs:45)。回调返 BoxFuture,Box::pin 构造 + 类型标注对齐签名。
                //
                // 借用处理:`handle_remote_command(payload, app, state)` 的 state 是
                // `State<'_, AppState>`(newtype `&AppState`),借用 app。若先 `let state =
                // app.state()` 再 move app 入函数,E0505(借用的 app 被 move)。
                // 正解:同一表达式内 `app.clone()` move clone、`app.state()` 借用原 app,
                // 原 app 不 move(仅被 state 借用到调用结束),借用检查通过。
                let cmd_app = tunnel_app.clone();
                let on_command: df_tunnel::tunnel::CommandHandler = std::sync::Arc::new(
                    move |payload: serde_json::Value| {
                        let app = cmd_app.clone();
                        Box::pin(async move {
                            crate::commands::ai::remote_bridge::handle_remote_command(
                                payload,
                                app.clone(),
                                app.state::<AppState>(),
                            )
                            .await;
                        })
                            as std::pin::Pin<
                                Box<dyn std::future::Future<Output = ()> + Send>,
                            >
                    },
                );

                // connect(失败非阻断,supervisor 兜底重连)。收发循环在 client 内部 spawn。
                // 注:on_command 用 clone 传入 connect(原值留给 supervisor 重连复用,Arc clone 廉价)。
                match state
                    .tunnel
                    .connect(&relay_url, &device_id, &token, on_command.clone())
                    .await
                {
                    Ok(()) => tracing::info!(
                        "[tunnel] 连接 relay 成功 device_id={} url={}",
                        device_id,
                        relay_url
                    ),
                    Err(e) => tracing::warn!("[tunnel] 连接 relay 失败(非阻断,supervisor 将重试): {}", e),
                }

                // supervisor:轮询 is_connected,断开则重连
                // 连接被拒(relay 未部署)→300s+INFO;其他错误→2s-60s WARN
                let mut backoff = std::time::Duration::from_secs(2);
                let mut last_was_refused = false;
                loop {
                    tokio::time::sleep(std::time::Duration::from_secs(5)).await;
                    if !state.tunnel.is_connected() {
                        tracing::info!("[tunnel-supervisor] 断线重连 url={relay_url} device_id={device_id}");
                        match state
                            .tunnel
                            .connect(&relay_url, &device_id, &token, on_command.clone())
                            .await
                        {
                            Ok(()) => {
                                tracing::info!("[tunnel-supervisor] 重连成功,退避重置");
                                backoff = std::time::Duration::from_secs(2);
                                last_was_refused = false;
                            }
                            Err(e) => {
                                let refused = is_connection_refused(&e);
                                if refused {
                                    // relay 未运行:INFO + 300s 退避,不刷 WARN
                                    if !last_was_refused {
                                        tracing::info!("[tunnel-supervisor] relay 未运行({e}),300s 后轻量探测");
                                        last_was_refused = true;
                                    }
                                    // 300s 退避(避免空转刷屏,relay 未运行时一直保持此间隔)
                                    tokio::time::sleep(std::time::Duration::from_secs(300)).await;
                                } else {
                                    tracing::warn!("[tunnel-supervisor] 重连失败 {e},退避 {backoff_secs}s", backoff_secs = backoff.as_secs());
                                    tokio::time::sleep(backoff).await;
                                    // 指数退避上限 60s,防网络长故障时高频重连打 relay
                                    backoff = (backoff * 2).min(std::time::Duration::from_secs(60));
                                    last_was_refused = false;
                                }
                            }
                        }
                    } else if last_was_refused {
                        last_was_refused = false;
                    }
                }
            });

            Ok(())
        })
        // 辅助函数 is_connection_refused 在文件尾部
        // 判断 TunnelError 是否为连接被拒(relay 未运行)。
        // 匹配 WS connect_async 产生的 IO error:ECONNREFUSED(Win:10061,Unix:111)。
        // 被拒 → relay 未部署,用长退避避免刷屏;其他错误(超时/DNS/网络瞬断)走指数退避。
        .invoke_handler(tauri::generate_handler![
            // 项目
            commands::project::list_projects,
            commands::project::create_project,
            commands::project::import_project,
            commands::project::get_project,
            commands::project::update_project,
            commands::project::delete_project,
            commands::project::list_deleted_projects,
            commands::project::restore_project,
            commands::project::purge_project,
            commands::project::scan_project_stack,
            commands::project::check_path_binding,
            commands::project::relocate_project_path,
            commands::project::check_path_exists,
            commands::project::scan_project_with_ai,
            commands::project::scan_directory_for_projects,
            commands::project::import_projects_batch,
            // 任务
            commands::task::list_tasks,
            commands::task::count_tasks,
            commands::task::create_task,
            commands::task::update_task,
            commands::task::delete_task,
            commands::task::restore_task,
            commands::task::get_task_by_id,
            commands::task::advance_task,
            // 知识图谱 Phase 1(对标设计 §五):任务横向关联 CRUD + 跨池移动 + 父子树
            commands::task::create_task_link,
            commands::task::remove_task_link,
            commands::task::list_task_links,
            commands::task::move_task_queue,
            commands::task::get_task_tree,
            // 知识图谱 Phase 2(对标设计 §五 + §2.4):项目事件流查询
            commands::events::get_project_timeline,
            // 知识图谱 Phase 3(对标设计 §五 + §2.3 + D10):项目基础设施配置 CRUD
            commands::services::add_project_service,
            commands::services::update_project_service,
            commands::services::remove_project_service,
            commands::services::list_project_services,
            // 工程系统(对标设计 §五 + V34):项目多工程 CRUD + Git 状态查询
            commands::module::add_project_module,
            commands::module::update_project_module,
            commands::module::remove_project_module,
            commands::module::list_project_modules,
            commands::module::scan_project_modules,
            commands::module::get_module_git_status,
            commands::module::list_branches,
            // 工程文件浏览(Batch 10):文件树 + 单文件预览
            commands::module::get_module_file_tree,
            commands::module::read_module_file,
            commands::module::get_module_file_meta,
            commands::module::get_module_file_diff,
            commands::module::get_module_commits,
            commands::module::get_commit_detail,
            commands::module::add_module_dependency,
            commands::module::remove_module_dependency,
            commands::module::list_module_dependencies,
            commands::module::detect_module_cycles,
            // 灵感
            commands::idea::list_ideas,
            commands::idea::create_idea,
            commands::idea::update_idea,
            commands::idea::relate_ideas,
            commands::idea::delete_idea,
            commands::idea::restore_idea,
            commands::idea::list_deleted_ideas,
            commands::idea::evaluate_idea,
            commands::idea::list_idea_evaluations,
            commands::idea::promote_idea,
            // 工作流
            commands::workflow::run_workflow,
            commands::workflow::list_workflow_executions,
            commands::workflow::get_workflow_execution,
            commands::workflow::approve_human_approval,
            commands::workflow::cancel_workflow_node,
            // AI 聊天
            commands::ai::ai_chat_send,
            commands::ai::ai_regenerate,
            commands::ai::ai_chat_edit,
            commands::ai::ai_chat_force_send,
            commands::ai::ai_chat_stop,
            commands::ai::ai_approve,
            // F-260616-03：达 max_iterations 暂停态续/停（消费 AiMaxRoundsReached，前端操作卡留 batch45）
            commands::ai::ai_continue_loop,
            commands::ai::ai_stop_loop,
            commands::ai::ai_pending_tool_calls,
            commands::ai::ai_chat_clear,
            commands::ai::ai_is_generating,
            // F-15 阶段2 手动上下文管理:分段归档 + LLM 压缩
            commands::ai::ai_chat_clear_context,
            commands::ai::ai_chat_compress_context,
            commands::ai::ai_list_providers,
            commands::ai::ai_save_provider,
            commands::ai::ai_set_provider,
            commands::ai::ai_delete_provider,
            // F-260614-04c: 负载均衡池可编辑层(enabled/weight 轻量更新 + caps 重建)
            commands::ai::ai_update_provider_pool,
            // F-01 阶段5:测试连接拉取模型列表 + 单模型探测
            commands::ai::ai_fetch_models,
            commands::ai::ai_probe_model,
            // P0-1:用表单当前值一次性测试连接(不写库,新建/编辑未保存均可测)
            commands::ai::ai_test_connection,
            // AI 对话管理
            commands::ai::ai_conversation_create,
            commands::ai::ai_conversation_list,
            commands::ai::ai_conversation_switch,
            commands::ai::ai_conversation_load_more,
            commands::ai::ai_conversation_delete,
            commands::ai::ai_conversation_rename,
            commands::ai::ai_conversation_archive,
            commands::ai::ai_conversation_set_pinned,
            commands::ai::ai_update_conversation_goals,
            commands::ai::ai_conversation_export,
            commands::ai::ai_list_skills,
            // 核心设计6: 热重载技能(invalidate + 重扫,不重启生效)
            commands::ai::ai_reload_skills,
            commands::ai::ai_set_concurrency_config,
            commands::ai::ai_set_agent_max_iterations,
            commands::ai::ai_set_agent_max_retries,
            // F-260619-03 Phase A: AI 工具文件访问授权目录白名单
            commands::ai::ai_get_allowed_dirs,
            commands::ai::ai_set_allowed_dirs,
            // F-260619-03 Phase B: 路径授权弹窗决策(消费 AiDirAuthRequired 挂起)
            commands::ai::ai_authorize_dir,
            // 审批历史面板(AE-2025-08:查 ai_tool_executions 表,敏感字段截断)
            commands::ai::audit::record::list_tool_executions,
            // 知识库
            commands::knowledge::knowledge_list,
            commands::knowledge::knowledge_get,
            commands::knowledge::knowledge_search,
            commands::knowledge::knowledge_create,
            commands::knowledge::knowledge_update_status,
            commands::knowledge::knowledge_record_reuse,
            commands::knowledge::knowledge_list_candidates,
            commands::knowledge::knowledge_archive,
            commands::knowledge::knowledge_retry_embedding,
            commands::knowledge::knowledge_get_config,
            commands::knowledge::knowledge_save_config,
            commands::knowledge::knowledge_extract_now,
            commands::knowledge::knowledge_get_detail,
            commands::knowledge::knowledge_update,
            commands::knowledge::knowledge_events,
            // 通用应用设置 KV(前端 localStorage 迁移目标)
            commands::settings::settings_get,
            commands::settings::settings_set,
            commands::settings::settings_get_all,
            commands::settings::settings_delete,
            commands::settings::get_data_dir,
            // 审批超时配置(默认 15 分钟,0=禁用;Settings 页可改)
            commands::settings::ai_get_approval_timeout,
            commands::settings::ai_set_approval_timeout,
            // ScriptNode 命令安全配置(白/黑名单,前端设置页写入)
            commands::settings::set_script_safety,
            commands::settings::get_script_safety,
            // Plan 执行开关
            commands::settings::set_plan_execution,
            commands::settings::get_plan_execution,
            // 冲突解决
            commands::settings::resolve_conflict,
            // 模板管理
            commands::settings::list_templates,
            commands::settings::save_template,
            commands::settings::delete_template,
            // CI 状态
            commands::ci_status::get_commit_status,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

/// 判断 TunnelError 是否为连接被拒(匹配 ECONNREFUSED 各平台表述)
fn is_connection_refused(e: &df_tunnel::TunnelError) -> bool {
    let msg = e.to_string().to_lowercase();
    msg.contains("refused")
        || msg.contains("10061")
        || msg.contains("积极拒绝")
        || msg.contains("actively refused")
}
