use serde::{Deserialize, Serialize};
use std::collections::HashMap;
use std::fs;
use std::path::PathBuf;

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ProjectEntry {
    pub path: String,
    pub name: String,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Category {
    pub id: String,
    pub name: String,
    pub order: i32,
}

/// 常用指令：projects 为 None 表示所有项目可见，否则仅在列出的项目路径下可见
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct CommandItem {
    pub id: String,
    pub name: String,
    pub content: String,
    #[serde(default)]
    pub category_id: Option<String>,
    #[serde(default)]
    pub projects: Option<Vec<String>>,
    /// 加星的指令在指令库和侧边栏面板中靠前显示
    #[serde(default)]
    pub starred: bool,
    #[serde(default)]
    pub updated_at: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct RuleItem {
    pub id: String,
    pub name: String,
    pub content: String,
    #[serde(default)]
    pub category_id: Option<String>,
    #[serde(default)]
    pub updated_at: u64,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct Settings {
    /// "system" | "light" | "dark"
    pub theme: String,
    /// 点击关闭按钮时最小化到托盘而不是退出
    pub close_to_tray: bool,
    /// 会话等待输入时发送系统通知
    pub notify_on_attention: bool,
    /// 会话回合结束时发送系统通知
    pub notify_on_done: bool,
    /// hooks 状态服务器监听端口
    pub hook_port: u16,
    /// claude 可执行文件路径；空串表示自动检测
    #[serde(default)]
    pub claude_path: String,
    /// 启用代码索引（符号图谱），为项目注册 ccgui-index MCP 服务器并后台建索引
    #[serde(default)]
    pub index_enabled: bool,
    /// 启用微信 ClawBot 对接（扫码后长轮询收发消息）
    #[serde(default)]
    pub wechat_enabled: bool,
    /// 允许微信入站消息驱动会话（写入/入队）；关闭则只收通知、不接受遥控
    #[serde(default = "default_true")]
    pub wechat_remote_input: bool,
    /// 推送：会话等待权限确认
    #[serde(default = "default_true")]
    pub wechat_push_attention: bool,
    /// 推送：回合完成摘要
    #[serde(default = "default_true")]
    pub wechat_push_done: bool,
    /// 推送：账号额度告警
    #[serde(default = "default_true")]
    pub wechat_push_quota: bool,
    /// 启用移动端 App 远程映射网关（局域网 WebSocket，实时镜像终端）
    #[serde(default)]
    pub mobile_enabled: bool,
    /// 允许移动端进行文件管理（浏览/编辑/复制/删除等；删除走回收站）。默认关闭，用时手动开
    #[serde(default)]
    pub mobile_fs_enabled: bool,
    /// 移动端网关监听端口
    #[serde(default = "default_mobile_port")]
    pub mobile_port: u16,
    /// 启用公网中继拨出：CCGUI 主动连到境内 VPS 上的中继，供手机在外网远程访问（明文转发 + 配对令牌）
    #[serde(default)]
    pub mobile_relay_enabled: bool,
    /// 中继地址（形如 wss://relay.你的域名/）；为空则不拨出
    #[serde(default)]
    pub mobile_relay_url: String,
    /// 启用公网隧道（子域名反向代理，把本机本地 HTTP 端口映射到公网；需同时开启公网中继）
    #[serde(default)]
    pub tunnel_enabled: bool,
    /// 隧道注册密钥（= 中继的 TUNNEL_SECRET）；注册时携带，不匹配中继即拒
    #[serde(default)]
    pub tunnel_secret: String,
    /// 隧道映射清单（子域名前缀 <-> 本机本地端口）
    #[serde(default)]
    pub tunnel_mappings: Vec<TunnelMapping>,
}

/// 隧道映射：本机本地 HTTP 端口 <-> 公网子域名前缀
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct TunnelMapping {
    /// 稳定 id（前端列表 key / 增删改定位）
    pub id: String,
    /// 子域名前缀（合法 DNS 标签，小写）
    pub subdomain: String,
    /// 本机本地端口
    pub local_port: u16,
    /// 是否启用（启用且中继在线时该子域名才对外可访问）
    #[serde(default = "default_true")]
    pub enabled: bool,
}

/// serde 默认值辅助：移动端网关默认端口
fn default_mobile_port() -> u16 {
    45820
}

/// serde 默认值辅助：缺省为 true（用于新增的开关型字段，旧配置升级后默认开启）
fn default_true() -> bool {
    true
}

impl Default for Settings {
    fn default() -> Self {
        Self {
            theme: "system".into(),
            close_to_tray: true,
            notify_on_attention: true,
            notify_on_done: true,
            hook_port: 45813,
            claude_path: String::new(),
            index_enabled: false,
            wechat_enabled: false,
            wechat_remote_input: true,
            wechat_push_attention: true,
            wechat_push_done: true,
            wechat_push_quota: true,
            mobile_enabled: false,
            mobile_fs_enabled: false,
            mobile_port: 45820,
            mobile_relay_enabled: false,
            mobile_relay_url: String::new(),
            tunnel_enabled: false,
            tunnel_secret: String::new(),
            tunnel_mappings: Vec::new(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize, Default)]
#[serde(rename_all = "camelCase", default)]
pub struct Config {
    pub projects: Vec<ProjectEntry>,
    pub commands: Vec<CommandItem>,
    pub command_categories: Vec<Category>,
    pub rules: Vec<RuleItem>,
    pub rule_categories: Vec<Category>,
    /// 会话标题覆盖（GUI 侧重命名）：sessionId -> title
    pub title_overrides: HashMap<String, String>,
    /// 内置指令是否已播种（旧版标记，等价于 seeded_version >= 1）
    pub seeded: bool,
    /// 内置指令播种版本：升级只补新增条目，不复活用户删除的旧条目
    pub seeded_version: u32,
    #[serde(default = "Settings::default")]
    pub settings: Settings,
}

/// 当前播种版本：清单变化时递增，并在 seed_builtin_commands 中追加对应增量
/// v3：修复废弃命令 id 拼写导致 /vim 未被清理的问题
const SEED_VERSION: u32 = 3;

/// v1 播种的 Claude Code 内置斜杠命令
const BUILTIN_V1: [(&str, &str); 20] = [
    ("/compact 压缩上下文", "/compact"),
    ("/clear 清空对话", "/clear"),
    ("/resume 恢复会话", "/resume"),
    ("/model 切换模型", "/model"),
    ("/review 代码审查 (PR)", "/review"),
    ("/init 生成 CLAUDE.md", "/init"),
    ("/memory 编辑记忆文件", "/memory"),
    ("/permissions 权限管理", "/permissions"),
    ("/hooks 配置 hooks", "/hooks"),
    ("/mcp 管理 MCP 服务器", "/mcp"),
    ("/agents 管理子代理", "/agents"),
    ("/context 上下文占用", "/context"),
    ("/cost 用量与费用", "/cost"),
    ("/usage 用量与限额", "/usage"),
    ("/status 状态信息", "/status"),
    ("/doctor 诊断安装", "/doctor"),
    ("/export 导出对话", "/export"),
    ("/todos 任务列表", "/todos"),
    ("/rewind 回退对话", "/rewind"),
    ("/vim Vim 输入模式", "/vim"),
];

/// v2 新增（依据官方 commands 文档补全）
const BUILTIN_V2: [(&str, &str); 78] = [
    ("/effort 模型力度档位", "/effort"),
    ("/plugin 插件管理", "/plugin"),
    ("/plan 进入计划模式", "/plan"),
    ("/fast 快速模式开关", "/fast"),
    ("/goal 设定目标直到达成", "/goal"),
    ("/fork 分叉子代理执行指令", "/fork"),
    ("/branch 分叉当前对话", "/branch"),
    ("/btw 不入上下文的快速提问", "/btw"),
    ("/recap 会话一句话总结", "/recap"),
    ("/rename 重命名会话", "/rename"),
    ("/focus 专注视图开关", "/focus"),
    ("/diff 查看未提交改动", "/diff"),
    ("/code-review 审查当前 diff", "/code-review"),
    ("/security-review 安全审查", "/security-review"),
    ("/simplify 清理简化改动", "/simplify"),
    ("/verify 验证改动可用", "/verify"),
    ("/run 启动项目应用", "/run"),
    ("/run-skill-generator 生成 run/verify 技能", "/run-skill-generator"),
    ("/batch 大规模并行改动", "/batch"),
    ("/loop 周期性重复执行", "/loop"),
    ("/schedule 定时任务管理", "/schedule"),
    ("/tasks 后台任务总览", "/tasks"),
    ("/stop 停止后台会话", "/stop"),
    ("/background 转为后台会话", "/background"),
    ("/autofix-pr 云端盯 PR 修 CI", "/autofix-pr"),
    ("/ultraplan 深度规划会话", "/ultraplan"),
    ("/ultrareview 云端多代理深度审查", "/ultrareview"),
    ("/deep-research 深度研究报告", "/deep-research"),
    ("/workflows 工作流进度视图", "/workflows"),
    ("/skills 列出技能", "/skills"),
    ("/reload-skills 重载技能", "/reload-skills"),
    ("/reload-plugins 重载插件", "/reload-plugins"),
    ("/add-dir 添加工作目录", "/add-dir"),
    ("/cd 切换工作目录", "/cd"),
    ("/sandbox 沙箱模式开关", "/sandbox"),
    ("/advisor 顾问模型开关", "/advisor"),
    ("/claude-api 加载 Claude API 参考", "/claude-api"),
    ("/debug 启用调试日志", "/debug"),
    ("/copy 复制上条回复", "/copy"),
    ("/stats 用量统计页", "/stats"),
    ("/usage-credits 超额用量设置", "/usage-credits"),
    ("/insights 会话分析报告", "/insights"),
    ("/config 设置界面", "/config"),
    ("/theme 主题切换", "/theme"),
    ("/color 提示栏颜色", "/color"),
    ("/statusline 配置状态栏", "/statusline"),
    ("/keybindings 键位配置", "/keybindings"),
    ("/terminal-setup 终端按键配置", "/terminal-setup"),
    ("/scroll-speed 滚轮速度", "/scroll-speed"),
    ("/tui 终端渲染模式", "/tui"),
    ("/voice 语音输入", "/voice"),
    ("/fewer-permission-prompts 减少权限弹窗", "/fewer-permission-prompts"),
    ("/ide IDE 集成管理", "/ide"),
    ("/desktop 转到桌面版", "/desktop"),
    ("/chrome Chrome 集成设置", "/chrome"),
    ("/mobile 手机 App 二维码", "/mobile"),
    ("/teleport 拉取网页版会话", "/teleport"),
    ("/remote-control 允许 claude.ai 远程控制", "/remote-control"),
    ("/remote-env 云代理默认环境", "/remote-env"),
    ("/web-setup 连接 GitHub 到网页版", "/web-setup"),
    ("/install-github-app 安装 GitHub 应用", "/install-github-app"),
    ("/install-slack-app 安装 Slack 应用", "/install-slack-app"),
    ("/setup-bedrock 配置 Bedrock", "/setup-bedrock"),
    ("/setup-vertex 配置 Vertex AI", "/setup-vertex"),
    ("/team-onboarding 团队上手指南", "/team-onboarding"),
    ("/login 登录", "/login"),
    ("/logout 登出", "/logout"),
    ("/privacy-settings 隐私设置", "/privacy-settings"),
    ("/release-notes 更新日志", "/release-notes"),
    ("/feedback 反馈问题", "/feedback"),
    ("/upgrade 升级套餐", "/upgrade"),
    ("/heapdump 内存快照诊断", "/heapdump"),
    ("/help 帮助", "/help"),
    ("/exit 退出 CLI", "/exit"),
    ("/powerup 功能互动教程", "/powerup"),
    ("/passes 赠送试用", "/passes"),
    ("/radio Claude FM 电台", "/radio"),
    ("/stickers 贴纸", "/stickers"),
];

/// 已被官方移除的内置命令：升级播种时清理（仅清理我们播种的条目）
const DEPRECATED_BUILTIN_IDS: [&str; 1] = ["builtin-vim"];

fn builtin_id(content: &str) -> String {
    format!("builtin{}", content.replace('/', "-"))
}

fn insert_builtins(config: &mut Config, list: &[(&str, &str)], category_id: &str) {
    for (name, content) in list {
        let id = builtin_id(content);
        if !config.commands.iter().any(|c| c.id == id) {
            config.commands.push(CommandItem {
                id,
                name: (*name).into(),
                content: (*content).into(),
                category_id: Some(category_id.to_string()),
                projects: None,
                starred: false,
                updated_at: now_ms(),
            });
        }
    }
}

/// 增量播种内置指令；返回是否有改动（调用方据此决定落盘）
pub fn seed_builtin_commands(config: &mut Config) -> bool {
    // 旧版只有 seeded 布尔标记，等价于版本 1
    let from = if config.seeded {
        config.seeded_version.max(1)
    } else {
        config.seeded_version
    };
    if from >= SEED_VERSION {
        return false;
    }
    let category_id = "builtin-claude";
    if !config.command_categories.iter().any(|c| c.id == category_id) {
        config.command_categories.insert(
            0,
            Category {
                id: category_id.into(),
                name: "内置指令".into(),
                order: 0,
            },
        );
    }
    if from < 1 {
        insert_builtins(config, &BUILTIN_V1, category_id);
    }
    if from < 2 {
        insert_builtins(config, &BUILTIN_V2, category_id);
    }
    if from < 3 {
        config
            .commands
            .retain(|c| !DEPRECATED_BUILTIN_IDS.contains(&c.id.as_str()));
    }
    config.seeded = true;
    config.seeded_version = SEED_VERSION;
    true
}

fn config_path() -> PathBuf {
    dirs::config_dir()
        .unwrap_or_else(|| PathBuf::from("."))
        .join("ccgui")
        .join("config.json")
}

pub fn load() -> Config {
    let path = config_path();
    match fs::read_to_string(&path) {
        Ok(raw) => serde_json::from_str(&raw).unwrap_or_default(),
        Err(_) => Config::default(),
    }
}

pub fn save(config: &Config) -> Result<(), String> {
    let path = config_path();
    let raw = serde_json::to_string_pretty(config).map_err(|e| e.to_string())?;
    crate::fsx::write_atomic(&path, raw.as_bytes())
}

pub fn now_ms() -> u64 {
    std::time::SystemTime::now()
        .duration_since(std::time::UNIX_EPOCH)
        .map(|d| d.as_millis() as u64)
        .unwrap_or(0)
}
