/**
 * @file drag_teach_recorder.h
 * @brief RM75 拖动示教轨迹录制器接口。
 *
 * @author mashiro
 * @date 2026-07-09
 * @last_modified 2026-07-09
 *
 * @details
 * 本文件声明拖动示教录制器。录制器属于 core 层，负责组织拖动示教生命周期：
 * 校验配置、创建轨迹文件路径、启动拖动、周期采样、停止拖动、写 CSV 和可选保存厂商轨迹。
 * 真实 SDK 调用全部通过 `IRobotArm` 抽象接口完成。
 *
 * 设计原则：
 * - core 不直接包含 RealMan SDK 头文件；
 * - 采样数据写入 trajectory 模块，不在 core 中处理 CSV 细节；
 * - 拖动示教启动后，无论采样成功或失败都必须尝试停止；
 * - 配置错误必须在连接硬件动作前尽早返回。
 *
 * @dependencies
 * - rm_control/common/result.h
 * - rm_control/common/robot_types.h
 * - rm_control/interfaces/robot_arm.h
 * - rm_control/modules/trajectory/trajectory_file_store.h
 */
#pragma once

#include "rm_control/common/result.h"
#include "rm_control/common/robot_types.h"
#include "rm_control/interfaces/robot_arm.h"
#include "rm_control/modules/trajectory/trajectory_file_store.h"

#include <iosfwd>
#include <string>
#include <vector>

namespace rm_control::core {

/**
 * @brief 拖动示教录制配置。
 *
 * @author mashiro
 * @date 2026-07-09
 * @last_modified 2026-07-09
 *
 * @details
 * 该结构体保存一次拖动示教录制所需的业务参数。它不保存硬件连接参数，
 * 因为连接生命周期由入口和 driver 管理。
 */
struct DragTeachRecordConfig {
    std::string storage_root;
    std::string session_name;
    int duration_seconds = 15;
    int sample_period_ms = 100;
    bool save_vendor_trajectory = true;
    common::DragTeachStartOptions drag_options;
};

/**
 * @brief 拖动示教录制结果摘要。
 *
 * @author mashiro
 * @date 2026-07-09
 * @last_modified 2026-07-09
 *
 * @details
 * 该结构体向入口层返回录制产物路径、采样数量和厂商轨迹保存结果。入口层只负责打印摘要，
 * 不需要理解 CSV 写入或厂商轨迹保存细节。
 */
struct DragTeachRecordSummary {
    modules::trajectory::TrajectoryFileSet files;
    int sample_count = 0;
    bool vendor_trajectory_saved = false;
    std::string vendor_save_message;
};

/**
 * @brief RM75 拖动示教录制器。
 *
 * @author mashiro
 * @date 2026-07-09
 * @last_modified 2026-07-09
 *
 * @details
 * 该类通过 `IRobotArm` 控制拖动示教生命周期，并把采样结果交给 trajectory 模块保存。
 * 类本身不拥有机械臂驱动，便于测试时注入 fake robot。
 *
 * @note
 * `record()` 会启动真实拖动示教，必须在人工确认安全环境后调用。
 *
 * @warning
 * 调用方需要确保机械臂已经连接且急停可触达。
 */
class DragTeachRecorder {
public:
    /**
     * @brief 构造拖动示教录制器。
     *
     * @author mashiro
     * @date 2026-07-09
     * @last_modified 2026-07-09
     *
     * @details
     * 构造函数只保存机械臂接口引用，不启动拖动、不读取状态、不创建文件。
     *
     * @param[in,out] robot 已连接或可用的机械臂抽象接口，生命周期必须长于录制器。
     *
     * @return 构造函数无显式返回值。
     *
     * @throws 本函数不主动抛出异常。
     *
     * @note
     * 真实连接状态由 `record()` 内部调用的 driver 方法返回。
     *
     * @warning
     * 不要传入临时对象引用。
     *
     * @thread_safety
     * 构造本身线程安全；同一录制器不支持并发录制。
     *
     * @par 边界条件
     * C++ 引用不能为空。
     *
     * @par 后续维护
     * 多机械臂录制应由更高层任务编排，不应让本类持有多个接口。
     */
    explicit DragTeachRecorder(interfaces::IRobotArm &robot);

    /**
     * @brief 执行一次拖动示教轨迹录制。
     *
     * @author mashiro
     * @date 2026-07-09
     * @last_modified 2026-07-09
     *
     * @details
     * 该函数按固定顺序执行：配置校验、轨迹文件路径准备、安全倒计时、启动拖动示教、
     * 周期采样当前机械臂状态、停止拖动示教、写入 CSV、可选保存厂商轨迹。
     *
     * @param[in] config 本次录制配置。
     * @param[out] out 过程日志输出流。
     *
     * @return 成功时返回录制摘要。
     * @return 失败时返回配置、启动、采样、停止、文件写入或厂商保存错误。
     *
     * @throws 本函数不主动抛出异常；底层错误通过 `Result` 返回。
     *
     * @note
     * 启动拖动示教后，即使采样失败，也会尝试停止拖动示教。
     *
     * @warning
     * 该函数会访问真实机械臂并进入拖动示教模式，必须有人监督。
     *
     * @thread_safety
     * 不支持并发调用；并发访问同一机械臂由调用方禁止。
     *
     * @par 边界条件
     * 空 session、空路径、非法时长或过短采样周期会在启动前失败。
     *
     * @par 后续维护
     * 新增采样字段时应先扩展 `TrajectorySample` 和 trajectory 模块，再修改本流程。
     */
    common::Result<DragTeachRecordSummary> record(const DragTeachRecordConfig &config,
                                                  std::ostream &out);

private:
    /**
     * @brief 校验拖动示教录制配置。
     *
     * @author mashiro
     * @date 2026-07-09
     * @last_modified 2026-07-09
     *
     * @details
     * 该函数在启动真实拖动示教前检查存储根目录、会话名、录制时长和采样周期。
     * 提前校验可以避免配置明显错误时仍进入硬件拖动状态。
     *
     * @param[in] config 待校验的录制配置。
     *
     * @return 成功时返回空成功结果。
     * @return 失败时返回具体配置错误。
     *
     * @throws 本函数不主动抛出异常。
     *
     * @note
     * 本函数只校验配置，不访问文件系统和真实硬件。
     *
     * @warning
     * 新增硬件相关参数时，必须在启动前加入校验。
     *
     * @thread_safety
     * 函数只读取输入配置，线程安全。
     *
     * @par 边界条件
     * 空路径、空会话名、非正时长、过短采样周期都会失败。
     *
     * @par 后续维护
     * 不要把文件创建或 SDK 状态读取混入配置校验函数。
     */
    common::Result<void> validateConfig(const DragTeachRecordConfig &config) const;

    /**
     * @brief 创建本次录制需要的轨迹文件路径集合。
     *
     * @author mashiro
     * @date 2026-07-09
     * @last_modified 2026-07-09
     *
     * @details
     * 该函数委托 trajectory 模块创建目录并生成 CSV、厂商轨迹、转换 C++ 和报告路径。
     * 单独封装是为了让 `record()` 只表达流程顺序。
     *
     * @param[in] config 录制配置，必须包含存储根目录和会话名。
     *
     * @return 成功时返回文件路径集合。
     * @return 失败时返回目录创建或路径准备错误。
     *
     * @throws 本函数不主动抛出异常。
     *
     * @note
     * 该函数可能创建本地目录，但不会连接机械臂。
     *
     * @warning
     * 路径规则由 trajectory 模块统一维护，不应在 core 层手工拼接。
     *
     * @thread_safety
     * 并发创建同一目录依赖底层文件系统处理。
     *
     * @par 边界条件
     * 空路径会由 trajectory 模块返回失败。
     *
     * @par 后续维护
     * 新增输出文件类型时应先扩展 `TrajectoryFileSet`。
     */
    common::Result<modules::trajectory::TrajectoryFileSet>
    createTrajectoryFileSet(const DragTeachRecordConfig &config) const;

    /**
     * @brief 在拖动示教状态下周期采集机械臂轨迹样本。
     *
     * @author mashiro
     * @date 2026-07-09
     * @last_modified 2026-07-09
     *
     * @details
     * 该函数按配置的采样周期读取当前机械臂状态，并转换为 `TrajectorySample` 列表。
     * 它假设拖动示教已经成功启动，不负责启动或停止硬件模式。
     *
     * @param[in] config 录制配置，提供时长和采样周期。
     * @param[out] out 采样过程日志输出流。
     *
     * @return 成功时返回样本列表。
     * @return 失败时返回状态读取错误。
     *
     * @throws 本函数不主动抛出异常。
     *
     * @note
     * 采样失败后由 `record()` 负责停止拖动示教。
     *
     * @warning
     * 本函数会阻塞当前线程直到采样结束或失败。
     *
     * @thread_safety
     * 不支持并发调用同一录制器采样同一机械臂。
     *
     * @par 边界条件
     * 配置非法应已由 `validateConfig()` 拒绝。
     *
     * @par 后续维护
     * 新增采样来源时应保持该函数只做采集，不写文件。
     */
    common::Result<std::vector<common::TrajectorySample>>
    collectTrajectorySamples(const DragTeachRecordConfig &config, std::ostream &out);

    /**
     * @brief 根据文件集合和样本列表构造录制摘要。
     *
     * @author mashiro
     * @date 2026-07-09
     * @last_modified 2026-07-09
     *
     * @details
     * 该函数只填充可直接从已知数据得到的摘要字段，例如文件路径和采样数量。
     * 厂商轨迹保存结果由单独函数补充。
     *
     * @param[in] files 本次录制产物路径集合。
     * @param[in] samples 已采集的轨迹样本列表。
     *
     * @return 录制摘要对象。
     *
     * @throws 本函数不主动抛出异常。
     *
     * @note
     * 函数不访问文件系统和硬件。
     *
     * @warning
     * 不要在该函数中触发厂商轨迹保存，避免摘要构造带副作用。
     *
     * @thread_safety
     * 函数只读输入并返回局部对象，线程安全。
     *
     * @par 边界条件
     * 空样本列表会返回 sample_count=0；上层写 CSV 会先拒绝空样本。
     *
     * @par 后续维护
     * 新增摘要字段时保持该函数无副作用。
     */
    DragTeachRecordSummary buildSummary(
        const modules::trajectory::TrajectoryFileSet &files,
        const std::vector<common::TrajectorySample> &samples) const;

    /**
     * @brief 根据配置可选保存厂商拖动示教轨迹。
     *
     * @author mashiro
     * @date 2026-07-09
     * @last_modified 2026-07-09
     *
     * @details
     * 该函数在 CSV 成功保存后，根据配置调用 driver 保存厂商轨迹，并把保存结果写入摘要。
     * 厂商轨迹保存失败不会覆盖已经成功写出的 CSV 主产物。
     *
     * @param[in] config 录制配置，提供是否保存厂商轨迹的开关。
     * @param[in,out] summary 录制摘要，函数会更新厂商轨迹字段。
     *
     * @return 无返回值；保存失败写入 `summary->vendor_save_message`。
     *
     * @throws 本函数不主动抛出异常。
     *
     * @note
     * CSV 是主产物，厂商轨迹是附加产物。
     *
     * @warning
     * `summary == nullptr` 时函数直接返回，避免空指针崩溃。
     *
     * @thread_safety
     * 函数会调用注入的 `IRobotArm`，线程安全取决于具体 driver。
     *
     * @par 边界条件
     * 禁用保存时只写入说明文本，不调用硬件保存接口。
     *
     * @par 后续维护
     * 如果厂商保存结果需要作为强失败，应新增配置开关，不要直接改变当前行为。
     */
    void saveVendorTrajectoryIfRequested(const DragTeachRecordConfig &config,
                                         DragTeachRecordSummary *summary);

    interfaces::IRobotArm &robot_;
};

} // namespace rm_control::core
