/**
 * @file drag_teach_cli_config.cpp
 * @brief RM75 拖动示教录制程序命令行参数解析实现。
 *
 * @author mashiro
 * @date 2026-07-09
 * @last_modified 2026-07-09
 *
 * @details
 * 本文件实现 `rm_drag_teach_record` 的参数解析。拖动示教属于真实硬件流程，
 * 因此解析阶段必须保持纯文本处理，不连接 SDK、不启动拖动、不写轨迹文件。
 * 入口文件拿到结构化配置后，再显式执行连接和录制流程。
 *
 * 设计原则：
 * - 保持原有位置参数顺序；
 * - 所有解析失败通过 `Result` 返回；
 * - 不在解析函数中调用 `std::exit`；
 * - 默认参数使用安全、可追踪的命名和路径规则。
 *
 * @dependencies
 * - rm_control/config/drag_teach_cli_config.h
 * - rm_control/modules/trajectory/trajectory_file_store.h
 */
#include "rm_control/config/drag_teach_cli_config.h"

#include "rm_control/modules/trajectory/trajectory_file_store.h"

#include <cerrno>
#include <cstdlib>
#include <limits>
#include <ostream>
#include <string>

namespace rm_control::config {
namespace {

/**
 * @brief 将进程名转换为可显示文本。
 *
 * @author mashiro
 * @date 2026-07-09
 * @last_modified 2026-07-09
 *
 * @details
 * usage 输出需要稳定的程序名。该函数统一处理空指针和空字符串，
 * 防止错误路径输出空 usage，影响用户排查命令行参数问题。
 *
 * @param[in] program_name `argv[0]`，允许为空。
 * @param[in] fallback_name 默认程序名。
 *
 * @return 可显示的程序名。
 *
 * @throws 本函数不主动抛出异常；标准库字符串分配失败由调用方处理。
 *
 * @note
 * 不做路径截断，保留 shell 传入的显示形式。
 *
 * @warning
 * 不要在此函数中访问文件系统或环境变量。
 *
 * @thread_safety
 * 函数只使用局部变量，线程安全。
 *
 * @par 边界条件
 * 空指针或空字符串返回 fallback。
 *
 * @par 后续维护
 * 若 CLI 模块数量继续增加，可统一抽到公共工具模块。
 */
std::string SafeProgramName(const char *program_name, const char *fallback_name) {
    if (program_name == nullptr || program_name[0] == '\0') {
        return fallback_name;
    }
    return program_name;
}

/**
 * @brief 解析十进制整数字符串。
 *
 * @author mashiro
 * @date 2026-07-09
 * @last_modified 2026-07-09
 *
 * @details
 * 拖动示教 CLI 中的录制时长和采样周期都来自用户输入。该函数集中处理空指针、
 * 空字符串、非数字尾缀和输出指针为空等边界，避免调用方重复写不完整的校验逻辑。
 *
 * @param[in] value 待解析的 C 字符串，允许为空指针。
 * @param[out] out 解析结果输出指针，不能为空。
 *
 * @return true 解析成功。
 * @return false 输入为空、格式错误或输出指针为空。
 *
 * @throws 本函数不主动抛出异常；`std::strtol` 不抛异常。
 *
 * @note
 * 数值范围的业务校验由 `DragTeachRecorder::validateConfig()` 负责。
 *
 * @warning
 * 超出 int 范围或 `strtol` 报告 ERANGE 时必须拒绝，不能截断为运动参数。
 *
 * @thread_safety
 * 函数只使用局部变量，线程安全。
 *
 * @par 边界条件
 * `value == nullptr`、空字符串、包含非数字字符时返回 false。
 *
 * @par 后续维护
 * 如需解析浮点或带单位的参数，应新增专用函数，不要扩展本函数职责。
 */
bool ParseDecimalInteger(const char *value, int *out) {
    if (value == nullptr || out == nullptr || value[0] == '\0') {
        return false;
    }

    errno = 0;
    char *end = nullptr;
    const long parsed_value = std::strtol(value, &end, 10);
    if (errno == ERANGE || end == value || *end != '\0' ||
        parsed_value < std::numeric_limits<int>::min() ||
        parsed_value > std::numeric_limits<int>::max()) {
        return false;
    }

    *out = static_cast<int>(parsed_value);
    return true;
}

/**
 * @brief 生成默认拖动示教会话名。
 *
 * @author mashiro
 * @date 2026-07-09
 * @last_modified 2026-07-09
 *
 * @details
 * 默认会话名使用 `rm75_drag_pose_` 前缀加轨迹模块提供的时间戳。这样录制产物
 * 在同一目录下不容易互相覆盖，也能从文件名追踪录制时间。
 *
 * @param 无输入参数。
 *
 * @return 默认会话名字符串。
 *
 * @throws 本函数不主动抛出异常；字符串拼接失败属于标准库内存异常。
 *
 * @note
 * 时间戳格式由 trajectory 模块维护。
 *
 * @warning
 * 不要在这里访问真实机械臂状态，默认命名应保持纯本地逻辑。
 *
 * @thread_safety
 * 依赖 `timestampString()`，该函数使用线程安全的 `localtime_r`。
 *
 * @par 边界条件
 * 系统时间不可用时由 C 运行库返回当前可用时间值。
 *
 * @par 后续维护
 * 如需加入操作者或任务名，应通过显式参数传入，不应读取全局配置。
 */
std::string BuildDefaultSessionName() {
    return "rm75_drag_pose_" + rm_control::modules::trajectory::timestampString();
}

/**
 * @brief 创建拖动示教 CLI 的默认配置。
 *
 * @author mashiro
 * @date 2026-07-09
 * @last_modified 2026-07-09
 *
 * @details
 * 该函数集中设置拖动示教程序默认值，包括会话名、存储根目录、拖动模式、
 * 奇异墙和厂商轨迹保存开关。集中默认值可以避免入口文件和测试各自构造不一致配置。
 *
 * @param 无输入参数。
 *
 * @return 带默认值的 `DragTeachRecordCliConfig`。
 *
 * @throws 本函数不主动抛出异常；字符串构造失败由标准库报告。
 *
 * @note
 * 默认模式为 RM75 普通记录拖动示教，六维力复合模式必须显式请求。
 *
 * @warning
 * 默认值不能绕过后续 `DragTeachRecorder` 的安全校验。
 *
 * @thread_safety
 * 函数使用局部对象，线程安全。
 *
 * @par 边界条件
 * 默认运行数据路径由编译期 `RM_RUNTIME_DATA_ROOT` 推导，不写入源码目录。
 *
 * @par 后续维护
 * 新增默认值时应同步 CLI 测试。
 */
DragTeachRecordCliConfig BuildDefaultDragTeachCliConfig() {
    DragTeachRecordCliConfig cli_config;
    cli_config.record_config.session_name = BuildDefaultSessionName();
    cli_config.record_config.storage_root =
        rm_control::modules::trajectory::defaultTrajectoryStorageRoot();
    cli_config.record_config.drag_options.mode =
        rm_control::common::DragTeachMode::OrdinaryRecorded;
    cli_config.record_config.drag_options.enable_singular_wall = true;
    cli_config.record_config.drag_options.record_vendor_trajectory = true;
    cli_config.record_config.save_vendor_trajectory = true;
    cli_config.safety_config_path =
        std::filesystem::path(RM_PROJECT_ROOT) / "config" / "drag_teach_safety.json";
    return cli_config;
}

} // namespace

void PrintDragTeachRecordUsage(const char *program_name, std::ostream &out) {
    const std::string display_name = SafeProgramName(program_name, "rm_drag_teach_record");
    out << "Usage: " << display_name
        << " [session_name] [duration_seconds] [sample_period_ms] [storage_root]"
        << " [--live] [--safety-config <path>] [--six-dof-force]"
        << " [--no-vendor-save] [--ordinary-recorded] [--no-singular-wall]\n"
        << "\n"
        << "Default mode is ordinary recorded drag teach so vendor save/replay has an explicit SDK contract.\n"
        << "Six-dof force mode is best-effort for vendor save because its SDK call has no record flag.\n"
        << "The program records synchronized CSV as the primary artifact.\n"
        << "--no-vendor-save is retained only for legacy parsing and is rejected by DragTeachService.\n"
        << "Safety: default is DryRun/refuse. Live use requires --live, enabled config,"
        << " runtime device readiness, and operator confirmation.\n";
}

common::Result<DragTeachRecordCliConfig> ParseDragTeachRecordCliArguments(int argc, char **argv) {
    DragTeachRecordCliConfig cli_config = BuildDefaultDragTeachCliConfig();
    if (argc < 0 || (argc > 0 && argv == nullptr)) {
        return common::Result<DragTeachRecordCliConfig>::failure(
            "invalid argc/argv input");
    }
    if (argc == 2) {
        const std::string first_argument = argv[1] == nullptr ? "" : argv[1];
        if (first_argument == "-h" || first_argument == "--help") {
            cli_config.help_requested = true;
            return common::Result<DragTeachRecordCliConfig>::success(cli_config);
        }
    }

    int positional_index = 0;
    for (int argument_index = 1; argument_index < argc; ++argument_index) {
        const std::string argument = argv[argument_index] == nullptr ? "" : argv[argument_index];
        if (argument == "--live") {
            cli_config.live_requested = true;
            continue;
        }
        if (argument == "--safety-config") {
            if (argument_index + 1 >= argc || argv[argument_index + 1] == nullptr ||
                argv[argument_index + 1][0] == '\0') {
                return common::Result<DragTeachRecordCliConfig>::failure(
                    "--safety-config requires a path");
            }
            cli_config.safety_config_path = argv[++argument_index];
            continue;
        }
        if (argument == "--six-dof-force") {
            cli_config.record_config.drag_options.mode =
                rm_control::common::DragTeachMode::SixDofForcePositionAndOrientation;
            continue;
        }
        if (argument == "--no-vendor-save") {
            cli_config.record_config.save_vendor_trajectory = false;
            continue;
        }
        if (argument == "--ordinary-recorded") {
            cli_config.record_config.drag_options.mode =
                rm_control::common::DragTeachMode::OrdinaryRecorded;
            continue;
        }
        if (argument == "--no-singular-wall") {
            cli_config.record_config.drag_options.enable_singular_wall = false;
            continue;
        }
        if (!argument.empty() && argument[0] == '-') {
            return common::Result<DragTeachRecordCliConfig>::failure("unknown option: " + argument);
        }

        ++positional_index;
        if (positional_index == 1) {
            cli_config.record_config.session_name = argument;
        } else if (positional_index == 2) {
            if (!ParseDecimalInteger(argument.c_str(), &cli_config.record_config.duration_seconds)) {
                return common::Result<DragTeachRecordCliConfig>::failure(
                    "invalid duration_seconds: " + argument);
            }
        } else if (positional_index == 3) {
            if (!ParseDecimalInteger(argument.c_str(), &cli_config.record_config.sample_period_ms)) {
                return common::Result<DragTeachRecordCliConfig>::failure(
                    "invalid sample_period_ms: " + argument);
            }
        } else if (positional_index == 4) {
            cli_config.record_config.storage_root = argument;
        } else {
            return common::Result<DragTeachRecordCliConfig>::failure(
                "too many positional arguments");
        }
    }

    return common::Result<DragTeachRecordCliConfig>::success(cli_config);
}

} // namespace rm_control::config
