#include "workshop_orchestrator_v2/workshop_orchestrator_v2_node.hpp" // 对应头文件实现。

#include <algorithm> // 检查报告文件是否已经存在。
#include <chrono> // sleep_for 需要的时间单位。
#include <filesystem> // 规范化现场数据输入和数据集索引路径。
#include <sstream> // 拼接 session_id 时要用字符串流。
#include <system_error> // 文件系统查询失败时用 error_code 接住错误。
#include <thread>  // 把整场执行放到后台线程时要用 std::thread。

#include "calibration_workshop_orchestration_interfaces/msg/approval_state.hpp"
#include "calibration_workshop_orchestration_interfaces/msg/precheck_item.hpp"

namespace workshop_orchestrator_v2
{
using calibration_workshop_orchestration_interfaces::msg::WorkshopEvent; // 进度通知消息短名。
using calibration_workshop_orchestration_interfaces::msg::WorkshopEventType; // 进度通知类型短名。
using calibration_workshop_orchestration_interfaces::srv::AcknowledgeManualStep; // 人工确认接口短名。
using calibration_workshop_orchestration_interfaces::srv::ApproveStageResult; // 审批接口短名。
using calibration_workshop_orchestration_interfaces::srv::CreateWorkshopSession; // 创建任务接口短名。
using calibration_workshop_orchestration_interfaces::srv::GetWorkshopReport; // 取报告接口短名。
using calibration_workshop_orchestration_interfaces::srv::GetWorkshopSession; // 查任务接口短名。
using calibration_workshop_orchestration_interfaces::srv::PauseWorkshopSession; // 暂停接口短名。
using calibration_workshop_orchestration_interfaces::srv::ResumeWorkshopSession; // 恢复接口短名。

namespace
{
namespace fs = std::filesystem;

std::string normalize_file_uri(const std::string & raw_path)
{
  if (raw_path.empty()) {
    return "";
  }

  std::error_code ec;
  const fs::path path(raw_path);
  const fs::path absolute_path = path.is_absolute() ? path : fs::absolute(path, ec);
  if (ec) {
    return path.lexically_normal().string();
  }
  return absolute_path.lexically_normal().string();
}

calibration_common_interfaces::msg::FileReference make_trace_file_reference(
  const std::string & raw_path,
  const std::string & description)
{
  calibration_common_interfaces::msg::FileReference ref;
  ref.file_uri = normalize_file_uri(raw_path);
  if (ref.file_uri.empty()) {
    return ref;
  }

  const fs::path path(ref.file_uri);
  ref.file_name = path.filename().string();
  ref.description = description;

  std::error_code ec;
  if (fs::exists(path, ec) && fs::is_regular_file(path, ec)) {
    ref.size_bytes = static_cast<int64_t>(fs::file_size(path, ec));
    if (ec) {
      ref.size_bytes = 0;
    }
  }
  return ref;
}

std::string infer_dataset_index_file(const std::string & data_input_params_file)
{
  const std::string normalized_data_input_file = normalize_file_uri(data_input_params_file);
  if (normalized_data_input_file.empty()) {
    return "";
  }

  const fs::path candidate = fs::path(normalized_data_input_file).parent_path() / "dataset_index.yaml";
  std::error_code ec;
  if (!fs::exists(candidate, ec) || !fs::is_regular_file(candidate, ec)) {
    return "";
  }
  return candidate.lexically_normal().string();
}

void append_metadata_value(
  WorkshopReport & report,
  const std::string & key,
  const std::string & value)
{
  if (value.empty()) {
    return;
  }

  calibration_common_interfaces::msg::KeyValuePair kv;
  kv.key = key;
  kv.value = value;
  report.metadata.push_back(kv);
}

void append_report_file(
  WorkshopReport & report,
  const calibration_common_interfaces::msg::FileReference & ref)
{
  if (ref.file_uri.empty()) {
    return;
  }

  const auto duplicate = std::find_if(
    report.report_files.begin(),
    report.report_files.end(),
    [&ref](const auto & existing) {
      return existing.file_uri == ref.file_uri;
    });
  if (duplicate == report.report_files.end()) {
    report.report_files.push_back(ref);
  }
}
}  // namespace

WorkshopOrchestratorV2Node::WorkshopOrchestratorV2Node(const rclcpp::NodeOptions & options)
: Node("workshop_orchestrator_v2", options) // 创建一个名为 workshop_orchestrator_v2 的 ROS 节点。
{
  wifi6_precheck_enabled_ = declare_parameter<bool>("wifi6_precheck_enabled", true);
  wifi6_precheck_require_all_links_ = declare_parameter<bool>("wifi6_precheck_require_all_links", true);
  data_input_params_file_ = declare_parameter<std::string>("data_input_params_file", "");
  dataset_index_file_ = declare_parameter<std::string>("dataset_index_file", "");
  Wifi6LinkClient::Config wifi6_config;
  wifi6_config.vehicle_host = declare_parameter<std::string>("wifi6_vehicle_host", "127.0.0.1");
  wifi6_config.vehicle_port = static_cast<uint16_t>(declare_parameter<int>("wifi6_vehicle_port", 9000));
  wifi6_config.timeout_ms = declare_parameter<int>("wifi6_timeout_ms", 5000);
  wifi6_link_client_ = std::make_unique<Wifi6LinkClient>(wifi6_config);
  parameter_callback_handle_ = this->add_on_set_parameters_callback(
    [this](const std::vector<rclcpp::Parameter> & parameters) {
      rcl_interfaces::msg::SetParametersResult result;
      result.successful = true;
      for (const auto & parameter : parameters) {
        if (parameter.get_name() == "wifi6_precheck_enabled") {
          wifi6_precheck_enabled_ = parameter.as_bool();
        } else if (parameter.get_name() == "wifi6_precheck_require_all_links") {
          wifi6_precheck_require_all_links_ = parameter.as_bool();
        } else if (parameter.get_name() == "data_input_params_file") {
          data_input_params_file_ = parameter.as_string();
        } else if (parameter.get_name() == "dataset_index_file") {
          dataset_index_file_ = parameter.as_string();
        }
      }
      return result;
    });

  event_publisher_ = this->create_publisher<WorkshopEvent>("/workshop_v2/events", 10); // 建一个 topic 发布器，后面把“任务进度通知”都发到这里。

  create_session_service_ = this->create_service<CreateWorkshopSession>(
    "/workshop_v2/create_session", // 外部以后调这个 service 来创建一条新的标定任务。
    std::bind(&WorkshopOrchestratorV2Node::handle_create_session, this, std::placeholders::_1, std::placeholders::_2));

  get_session_service_ = this->create_service<GetWorkshopSession>(
    "/workshop_v2/get_session", // 外部以后调这个 service 来看任务现在跑到哪了。
    std::bind(&WorkshopOrchestratorV2Node::handle_get_session, this, std::placeholders::_1, std::placeholders::_2));

  get_report_service_ = this->create_service<GetWorkshopReport>(
    "/workshop_v2/get_report", // 外部以后调这个 service 来拿最终报告。
    std::bind(&WorkshopOrchestratorV2Node::handle_get_report, this, std::placeholders::_1, std::placeholders::_2));

  pause_session_service_ = this->create_service<PauseWorkshopSession>(
    "/workshop_v2/pause_session",
    std::bind(&WorkshopOrchestratorV2Node::handle_pause_session, this, std::placeholders::_1, std::placeholders::_2));

  resume_session_service_ = this->create_service<ResumeWorkshopSession>(
    "/workshop_v2/resume_session",
    std::bind(&WorkshopOrchestratorV2Node::handle_resume_session, this, std::placeholders::_1, std::placeholders::_2));

  acknowledge_manual_step_service_ = this->create_service<AcknowledgeManualStep>(
    "/workshop_v2/acknowledge_manual_step",
    std::bind(&WorkshopOrchestratorV2Node::handle_acknowledge_manual_step, this, std::placeholders::_1, std::placeholders::_2));

  approve_stage_result_service_ = this->create_service<ApproveStageResult>(
    "/workshop_v2/approve_stage_result",
    std::bind(&WorkshopOrchestratorV2Node::handle_approve_stage_result, this, std::placeholders::_1, std::placeholders::_2));

  execute_session_action_server_ = rclcpp_action::create_server<ExecuteWorkshopSession>(
    this,
    "/workshop_v2/execute_session", // 外部以后向这个 action 发起“开始跑这台车这次标定”的长任务请求。
    std::bind(&WorkshopOrchestratorV2Node::handle_execute_goal, this, std::placeholders::_1, std::placeholders::_2),
    std::bind(&WorkshopOrchestratorV2Node::handle_execute_cancel, this, std::placeholders::_1),
    std::bind(&WorkshopOrchestratorV2Node::handle_execute_accepted, this, std::placeholders::_1));

  // 各模块薄 client 统一在这里初始化。
  // 每个 client 只负责：发请求、等结果、翻译结果，不持有专项实现细节。
  external_localization_client_ = std::make_unique<ExternalLocalizationClient>(this);
  chassis_gateway_client_       = std::make_unique<ChassisGatewayClient>(this);
  control_gateway_client_       = std::make_unique<ControlGatewayClient>(this);
  sensor_gateway_client_        = std::make_unique<SensorGatewayClient>(this);
}

void WorkshopOrchestratorV2Node::handle_create_session(
  const std::shared_ptr<CreateWorkshopSession::Request> request,
  std::shared_ptr<CreateWorkshopSession::Response> response)
{
  SessionRecord record; // 先准备一条新的任务记录，后面把这次标定的基础信息填进去。
  record.session.session_id = make_session_id(); // 给这次标定任务生成唯一 ID；后续查询、执行、拿报告都靠它。
  record.session.state = make_session_state(WorkshopSessionState::DRAFT); // 新任务刚创建时先记成 DRAFT，意思是“任务档案先建好了，但还没正式开跑”。
  record.session.vehicle_profile_snapshot = request->request.vehicle_profile_snapshot; // 保存创建当下这台车的画像快照，后面生成步骤时就按这份信息来。
  record.session.operator_info = request->request.operator_info; // 记下是谁发起了这次标定，后面查责任和追溯都要用。
  record.session.config = request->request.config; // 保存这次任务自己的配置，比如执行开关、策略参数等。
  record.session.workshop_line_id = request->request.workshop_line_id; // 记下这次任务属于哪条产线/工位，方便现场区分。
  record.session.created_timestamp_us = now_us(); // 记录任务创建时间。
  record.session.updated_timestamp_us = record.session.created_timestamp_us; // 刚创建完成时，最近更新时间就等于创建时间。

  if (request->request.auto_build_execution_plan) { // 如果调用方要求“创建任务时顺手把步骤也列出来”。
    record.session.stage_plan = plan_builder_.build_minimal_plan(
      record.session.vehicle_profile_snapshot,
      record.session.config); // 立刻生成这次任务要跑的步骤列表。
    record.session.state = make_session_state(WorkshopSessionState::READY); // 既然步骤已经有了，就把任务状态推进到 READY，表示“可以准备开跑了”。
  }

  {
    std::lock_guard<std::mutex> lock(mutex_); // 加锁，防止后台执行线程和当前 service 同时改 sessions_。
    sessions_[record.session.session_id] = record; // 把这条新任务写进内存表里，key 就是任务 ID。
  }

  response->response.success = true; // 表示“创建任务”这个 service 调用成功了。
  response->response.error_code = make_error_code(ErrorCode::OK); // 没有错误，返回 OK。
  response->response.message = "Session created."; // 给调用方一句简短说明。
  response->response.session_id = record.session.session_id; // 把新任务 ID 返回给外部，外部后面必须靠它继续操作。
  response->response.state = record.session.state; // 把当前状态返回给外部，外部马上就知道是 DRAFT 还是 READY。
  response->response.session = record.session; // 把整条任务快照一起返回，外部不用马上再查一次。

  publish_event(record, WorkshopEventType::SESSION_STATE_CHANGED, "Session created."); // 发一条“新任务已经创建”通知，界面和日志系统可以立刻显示。
  if (request->request.auto_build_execution_plan) {
    publish_event(record, WorkshopEventType::EXECUTION_PLAN_BUILT, "Execution plan built.");
  }
}

void WorkshopOrchestratorV2Node::handle_get_session(
  const std::shared_ptr<GetWorkshopSession::Request> request,
  std::shared_ptr<GetWorkshopSession::Response> response)
{
  std::lock_guard<std::mutex> lock(mutex_); // 查任务前先加锁，避免读到一半后台线程正在修改。
  const auto it = sessions_.find(request->request.session_id); // 按请求里给的任务 ID 去内存表里查。
  if (it == sessions_.end()) { // 这说明外部给的任务 ID 根本不存在。
    response->response.success = false;
    response->response.error_code = make_error_code(ErrorCode::INVALID_ARGUMENT);
    response->response.message = "Session not found.";
    return;
  }

  response->response.success = true; // 查询成功。
  response->response.error_code = make_error_code(ErrorCode::OK);
  response->response.message = "OK";
  response->response.session = it->second.session; // 把当前任务快照原样返回给外部。
}

void WorkshopOrchestratorV2Node::handle_get_report(
  const std::shared_ptr<GetWorkshopReport::Request> request,
  std::shared_ptr<GetWorkshopReport::Response> response)
{
  std::lock_guard<std::mutex> lock(mutex_); // 查报告前先加锁，避免后台线程正在写报告。
  const auto it = sessions_.find(request->request.session_id); // 先找到对应任务。
  if (it == sessions_.end()) {
    response->response.success = false;
    response->response.error_code = make_error_code(ErrorCode::INVALID_ARGUMENT);
    response->response.message = "Session not found.";
    return;
  }

  if (!it->second.report_ready) { // 任务存在，但报告还没生成好。
    response->response.success = false;
    response->response.error_code = make_error_code(ErrorCode::INVALID_STATE);
    response->response.message = "Report not ready.";
    return;
  }

  response->response.success = true;
  response->response.error_code = make_error_code(ErrorCode::OK);
  response->response.message = "OK";
  response->response.report = it->second.report; // 把最终报告返回出去。
}

void WorkshopOrchestratorV2Node::handle_pause_session(
  const std::shared_ptr<PauseWorkshopSession::Request> request,
  std::shared_ptr<PauseWorkshopSession::Response> response)
{
  std::lock_guard<std::mutex> lock(mutex_);
  const auto it = sessions_.find(request->request.session_id);
  if (it == sessions_.end()) {
    response->response.success = false;
    response->response.error_code = make_error_code(ErrorCode::INVALID_ARGUMENT);
    response->response.message = "Session not found.";
    return;
  }
  if (it->second.session.state.value != WorkshopSessionState::RUNNING) {
    response->response.success = false;
    response->response.error_code = make_error_code(ErrorCode::INVALID_STATE);
    response->response.message = "Only running sessions can be paused.";
    return;
  }

  pause_requested_ = true;
  response->response.success = true;
  response->response.error_code = make_error_code(ErrorCode::OK);
  response->response.message = "Pause requested.";
}

void WorkshopOrchestratorV2Node::handle_resume_session(
  const std::shared_ptr<ResumeWorkshopSession::Request> request,
  std::shared_ptr<ResumeWorkshopSession::Response> response)
{
  std::lock_guard<std::mutex> lock(mutex_);
  const auto it = sessions_.find(request->request.session_id);
  if (it == sessions_.end()) {
    response->response.success = false;
    response->response.error_code = make_error_code(ErrorCode::INVALID_ARGUMENT);
    response->response.message = "Session not found.";
    return;
  }
  if (it->second.session.state.value != WorkshopSessionState::PAUSED) {
    response->response.success = false;
    response->response.error_code = make_error_code(ErrorCode::INVALID_STATE);
    response->response.message = "Only paused sessions can be resumed.";
    return;
  }

  pause_requested_ = false;
  pause_cv_.notify_all();
  response->response.success = true;
  response->response.error_code = make_error_code(ErrorCode::OK);
  response->response.message = "Session resumed.";
}

void WorkshopOrchestratorV2Node::handle_acknowledge_manual_step(
  const std::shared_ptr<AcknowledgeManualStep::Request> request,
  std::shared_ptr<AcknowledgeManualStep::Response> response)
{
  std::lock_guard<std::mutex> lock(mutex_);
  const auto it = sessions_.find(request->request.session_id);
  if (it == sessions_.end()) {
    response->response.success = false;
    response->response.error_code = make_error_code(ErrorCode::INVALID_ARGUMENT);
    response->response.message = "Session not found.";
    return;
  }

  auto & record = it->second;
  if (!record.manual_action_pending || record.waiting_stage_id != request->request.stage_id) {
    response->response.success = false;
    response->response.error_code = make_error_code(ErrorCode::INVALID_STATE);
    response->response.message = "Session is not waiting for this manual action.";
    return;
  }
  if (!request->request.confirmed) {
    response->response.success = false;
    response->response.error_code = make_error_code(ErrorCode::INVALID_ARGUMENT);
    response->response.message = "Manual action must be confirmed.";
    return;
  }

  record.manual_action_confirmed = true;
  record.manual_action_pending = false;
  record.waiting_stage_id.clear();
  record.session.state = make_session_state(WorkshopSessionState::RUNNING);
  record.session.updated_timestamp_us = now_us();
  publish_event(record, WorkshopEventType::SESSION_STATE_CHANGED, "Manual action confirmed.");
  manual_action_cv_.notify_all();
  response->response.success = true;
  response->response.error_code = make_error_code(ErrorCode::OK);
  response->response.message = "Manual action confirmed.";
}

void WorkshopOrchestratorV2Node::handle_approve_stage_result(
  const std::shared_ptr<ApproveStageResult::Request> request,
  std::shared_ptr<ApproveStageResult::Response> response)
{
  std::lock_guard<std::mutex> lock(mutex_);
  const auto it = sessions_.find(request->request.session_id);
  if (it == sessions_.end()) {
    response->response.success = false;
    response->response.error_code = make_error_code(ErrorCode::INVALID_ARGUMENT);
    response->response.message = "Session not found.";
    return;
  }

  auto & record = it->second;
  if (!record.approval_pending || record.waiting_stage_id != request->request.stage_id) {
    response->response.success = false;
    response->response.error_code = make_error_code(ErrorCode::INVALID_STATE);
    response->response.message = "Session is not waiting for this approval.";
    return;
  }
  if (request->request.decision.value == ApprovalState::APPROVAL_STATE_UNSPECIFIED) {
    response->response.success = false;
    response->response.error_code = make_error_code(ErrorCode::INVALID_ARGUMENT);
    response->response.message = "Approval decision is required.";
    return;
  }

  record.approval_decided = true;
  record.approval_approved = request->request.decision.value == ApprovalState::APPROVED;
  record.approval_pending = false;
  record.session.state = record.approval_approved
    ? make_session_state(WorkshopSessionState::RUNNING)
    : make_session_state(WorkshopSessionState::FAILED);
  record.session.updated_timestamp_us = now_us();
  publish_event(
    record,
    record.approval_approved ? WorkshopEventType::SESSION_STATE_CHANGED : WorkshopEventType::STAGE_FAILED,
    record.approval_approved ? "Stage approved." : "Stage approval rejected.");
  approval_cv_.notify_all();
  response->response.success = true;
  response->response.error_code = make_error_code(ErrorCode::OK);
  response->response.message = record.approval_approved ? "Stage approved." : "Stage rejected.";
}

rclcpp_action::GoalResponse WorkshopOrchestratorV2Node::handle_execute_goal(
  const rclcpp_action::GoalUUID & /*uuid*/,
  std::shared_ptr<const ExecuteWorkshopSession::Goal> goal)
{
  std::lock_guard<std::mutex> lock(mutex_); // 判断能不能执行前先加锁，避免别人同时改任务状态。
  const auto it = sessions_.find(goal->goal.session_id); // 从执行请求里拿到任务 ID，再去找对应任务。
  if (it == sessions_.end()) { // 外部要求执行一条并不存在的任务。
    RCLCPP_WARN(get_logger(), "Reject execute_session goal: session not found.");
    return rclcpp_action::GoalResponse::REJECT;
  }
  if (it->second.executing) {
    RCLCPP_WARN(get_logger(), "Reject execute_session goal: session already executing.");
    return rclcpp_action::GoalResponse::REJECT;
  }
  if (it->second.session.state.value != WorkshopSessionState::READY &&
      it->second.session.state.value != WorkshopSessionState::DRAFT) { // 只有 READY 和 DRAFT 两种状态允许开始跑。
    RCLCPP_WARN(get_logger(), "Reject execute_session goal: invalid session state %u.", it->second.session.state.value);
    return rclcpp_action::GoalResponse::REJECT;
  }
  return rclcpp_action::GoalResponse::ACCEPT_AND_EXECUTE; // 条件满足，同意开始跑这条任务。
}

rclcpp_action::CancelResponse WorkshopOrchestratorV2Node::handle_execute_cancel(
  const std::shared_ptr<GoalHandleExecuteWorkshopSession> /*goal_handle*/)
{
  return rclcpp_action::CancelResponse::ACCEPT; // 先接受取消请求；真正停下来的时机在执行线程里判断。
}

void WorkshopOrchestratorV2Node::handle_execute_accepted(
  const std::shared_ptr<GoalHandleExecuteWorkshopSession> goal_handle)
{
  std::thread(std::bind(&WorkshopOrchestratorV2Node::execute_session, this, goal_handle)).detach(); // 新开后台线程去跑整场任务，避免把 ROS 回调线程卡住。
}

void WorkshopOrchestratorV2Node::execute_session(
  const std::shared_ptr<GoalHandleExecuteWorkshopSession> goal_handle)
{
  const auto goal = goal_handle->get_goal(); // 取出外部发来的“开始跑这台车这次标定”的请求内容。
  auto context = prepare_session_for_run(goal->goal.session_id); // 把这条任务切到“准备开跑”状态，并记录真正开始时间。

  const auto precheck = run_precheck_step(context.session_id); // 正式开跑前先检查：这条任务现在有没有条件开始跑。
  if (!precheck.ready_for_start) { // 如果检查结论是不允许开跑。
    finish_session_precheck_failed(goal_handle, context, precheck); // 直接按“预检失败”路径收尾并返回。
    return;
  }

  mark_session_running(context.session_id); // 预检通过后，把整条任务切到“正式运行中”。

  std::string failure_reason; // 如果中途某一步失败，就把失败原因写到这里。
  if (handle_cancel_if_requested(goal_handle, context)) { // 先看外部有没有在正式执行前就要求取消。
    return; // 如果已经取消并收尾，这里直接结束。
  }

  if (!run_all_stages(goal_handle, context, failure_reason)) { // 按顺序执行每一个步骤。
    if (failure_reason.empty()) { // 失败原因为空，通常表示走的是“取消”分支。
      return;
    }
    finish_session_failed(goal_handle, context, failure_reason); // 某一步执行失败时，按失败路径收尾。
    return;
  }

  finish_session_succeeded(goal_handle, context); // 全部步骤都成功后，按成功路径收尾。
}

WorkshopOrchestratorV2Node::SessionExecutionContext WorkshopOrchestratorV2Node::prepare_session_for_run(
  const std::string & session_id)
{
  SessionExecutionContext context; // 这是一份“本次执行上下文”，只给 execute_session 这次调用自己用。
  context.session_id = session_id; // 记下当前正在跑的是哪条任务。
  context.started_timestamp_us = now_us(); // 记下真正开始执行的时间，后面生成报告时要用。

  std::lock_guard<std::mutex> lock(mutex_);
  auto & record = sessions_.at(session_id); // 取出这条任务记录。
  if (record.session.stage_plan.empty()) { // 如果这条任务之前还没生成步骤列表。
    record.session.stage_plan = plan_builder_.build_minimal_plan(
      record.session.vehicle_profile_snapshot,
      record.session.config); // 就在正式开跑前补一份最小步骤列表。
  }
  record.executing = true;
  record.report_ready = false;
  record.stage_results.clear();
  record.session.state = make_session_state(WorkshopSessionState::PRECHECK_RUNNING); // 任务状态先切到“正在做开跑前检查”。
  record.session.updated_timestamp_us = now_us();
  publish_event(record, WorkshopEventType::PRECHECK_STARTED, "Precheck started."); // 给外部发通知：预检开始了。
  return context;
}

WorkshopPrecheckResponse WorkshopOrchestratorV2Node::run_precheck_step(const std::string & session_id)
{
  WorkshopSession session_snapshot;
  {
    std::lock_guard<std::mutex> lock(mutex_);
    session_snapshot = sessions_.at(session_id).session;
  }

  auto precheck = precheck_runner_.run(session_snapshot); // 先检查计划和 stage metadata。
  run_wifi6_link_precheck(session_snapshot, precheck); // 再检查车间工控机 <-> 车端电脑四条 WiFi6/TCP 链路。

  std::lock_guard<std::mutex> lock(mutex_);
  auto & record = sessions_.at(session_id);
  record.last_precheck = precheck; // 把这次预检结果存下来，后面查“为什么没开跑”时可以直接返回。
  record.session.updated_timestamp_us = now_us();
  publish_event(record, WorkshopEventType::PRECHECK_COMPLETED, precheck.message); // 给外部发通知：预检做完了，并把结论文本一起带出去。
  return precheck;
}

void WorkshopOrchestratorV2Node::append_precheck_item(
  WorkshopPrecheckResponse & precheck,
  const std::string & item_code,
  const std::string & display_name,
  bool passed,
  const std::string & message,
  uint8_t stage_type,
  uint8_t module_type)
{
  calibration_workshop_orchestration_interfaces::msg::PrecheckItem item;
  item.item_code = item_code;
  item.display_name = display_name;
  item.passed = passed;
  item.blocking = true;
  item.error_code = make_error_code(passed ? ErrorCode::OK : ErrorCode::NETWORK_LOSS);
  item.message = message;
  item.related_stage_type = make_stage_type(stage_type);
  item.related_module_type = make_module_type(module_type);
  precheck.items.push_back(item);
  if (!passed) {
    precheck.blocking_issue_count += 1;
    precheck.all_passed = false;
    precheck.success = false;
    precheck.error_code = make_error_code(ErrorCode::NETWORK_LOSS);
  }
}

void WorkshopOrchestratorV2Node::run_wifi6_link_precheck(
  const WorkshopSession & session,
  WorkshopPrecheckResponse & precheck)
{
  if (!wifi6_precheck_enabled_ || !wifi6_link_client_) {
    return;
  }

  bool needs_chassis = wifi6_precheck_require_all_links_;
  bool needs_control = wifi6_precheck_require_all_links_;
  bool needs_sensor = wifi6_precheck_require_all_links_;
  bool needs_external_pose = wifi6_precheck_require_all_links_;
  if (!wifi6_precheck_require_all_links_) {
    for (const auto & stage : session.stage_plan) {
      needs_chassis = needs_chassis || stage.module_type.value == CalibrationModuleType::CHASSIS_MODULE;
      needs_control = needs_control || stage.module_type.value == CalibrationModuleType::CONTROL_MODULE;
      needs_sensor = needs_sensor ||
        stage.module_type.value == CalibrationModuleType::SENSOR_INTRINSIC_MODULE ||
        stage.module_type.value == CalibrationModuleType::SENSOR_EXTRINSIC_MODULE ||
        stage.module_type.value == CalibrationModuleType::HAND_EYE_MODULE;
      needs_external_pose = needs_external_pose ||
        stage.module_type.value == CalibrationModuleType::CONTROL_MODULE ||
        stage.module_type.value == CalibrationModuleType::EXTERNAL_LOCALIZATION_MODULE;
    }
  }

  if (needs_chassis) {
    const auto result = wifi6_link_client_->check_chassis();
    append_precheck_item(
      precheck,
      "wifi6.chassis",
      "底盘 WiFi6/TCP 链路",
      result.passed,
      result.message,
      WorkflowStageType::CHASSIS_CALIBRATION_STAGE,
      CalibrationModuleType::CHASSIS_MODULE);
  }
  if (needs_control) {
    const auto result = wifi6_link_client_->check_control();
    append_precheck_item(
      precheck,
      "wifi6.control",
      "运控 WiFi6/TCP 链路",
      result.passed,
      result.message,
      WorkflowStageType::CONTROL_CALIBRATION_STAGE,
      CalibrationModuleType::CONTROL_MODULE);
  }
  if (needs_sensor) {
    const auto result = wifi6_link_client_->check_sensor();
    append_precheck_item(
      precheck,
      "wifi6.sensor",
      "传感器 WiFi6/TCP 链路",
      result.passed,
      result.message,
      WorkflowStageType::SENSOR_INTRINSIC_CALIBRATION_STAGE,
      CalibrationModuleType::SENSOR_INTRINSIC_MODULE);
  }
  if (needs_external_pose) {
    const auto result = wifi6_link_client_->check_external_pose();
    append_precheck_item(
      precheck,
      "wifi6.external_pose",
      "外部真值位姿 WiFi6/TCP 链路",
      result.passed,
      result.message,
      WorkflowStageType::EXTERNAL_REFERENCE_READY_CHECK_STAGE,
      CalibrationModuleType::EXTERNAL_LOCALIZATION_MODULE);
  }

  precheck.ready_for_start = precheck.all_passed;
  precheck.message = precheck.ready_for_start ?
    "Precheck passed: metadata and WiFi6 links are ready." :
    "Precheck failed: metadata or WiFi6 link check failed.";
}

void WorkshopOrchestratorV2Node::mark_session_running(const std::string & session_id)
{
  std::lock_guard<std::mutex> lock(mutex_);
  auto & record = sessions_.at(session_id);
  record.session.state = make_session_state(WorkshopSessionState::RUNNING); // 现在正式进入“主控正在按步骤跑任务”的状态。
  record.session.updated_timestamp_us = now_us();
  publish_event(record, WorkshopEventType::SESSION_STATE_CHANGED, "Session running."); // 给外部发通知：整场已经正式开始跑了。
}

bool WorkshopOrchestratorV2Node::handle_cancel_if_requested(
  const std::shared_ptr<GoalHandleExecuteWorkshopSession> goal_handle,
  const SessionExecutionContext & context)
{
  if (!goal_handle->is_canceling()) {
    return false;
  }
  {
    std::lock_guard<std::mutex> lock(mutex_);
    auto & record = sessions_.at(context.session_id);
    record.manual_action_pending = false;
    record.approval_pending = false;
  }
  finish_session_canceled(goal_handle, context);
  return true;
}

bool WorkshopOrchestratorV2Node::run_all_stages(
  const std::shared_ptr<GoalHandleExecuteWorkshopSession> goal_handle,
  SessionExecutionContext & context,
  std::string & failure_reason)
{
  std::vector<StagePlan> stage_plan;
  {
    std::lock_guard<std::mutex> lock(mutex_);
    const auto & record = sessions_.at(context.session_id);
    stage_plan = record.session.stage_plan;
  }

  if (stage_plan.empty()) {
    failure_reason = "Session has no stages to execute.";
    return false;
  }

  for (const auto & stage : stage_plan) {
    if (handle_cancel_if_requested(goal_handle, context)) {
      failure_reason.clear();
      return false;
    }

    bool stage_succeeded = false;
    std::string stage_failure_reason;
    for (uint32_t attempt = 0; attempt <= stage.retry_limit; ++attempt) {
      failure_reason.clear();
      if (run_single_stage(goal_handle, context.session_id, stage, failure_reason)) {
        stage_succeeded = true;
        break;
      }
      stage_failure_reason = failure_reason;
      if (goal_handle->is_canceling()) {
        failure_reason.clear();
        return false;
      }
      if (attempt < stage.retry_limit) {
        {
          std::lock_guard<std::mutex> lock(mutex_);
          auto & record = sessions_.at(context.session_id);
          record.session.updated_timestamp_us = now_us();
          publish_event(
            record,
            WorkshopEventType::SESSION_STATE_CHANGED,
            "Stage retry scheduled.",
            stage.stage_id,
            JobState::PENDING,
            stage.stage_type.value,
            stage.module_type.value);
        }
        continue;
      }
      break;
    }

    if (!stage_succeeded) {
      if (stage.execution_policy.value == StageExecutionPolicy::OPTIONAL ||
          stage.execution_policy.value == StageExecutionPolicy::SKIP_IF_UNSUPPORTED) {
        failure_reason.clear();
        continue;
      }
      failure_reason = stage_failure_reason.empty() ? "Stage execution failed." : stage_failure_reason;
      return false;
    }
  }

  return true;
}

bool WorkshopOrchestratorV2Node::run_single_stage(
  const std::shared_ptr<GoalHandleExecuteWorkshopSession> goal_handle,
  const std::string & session_id,
  const StagePlan & stage,
  std::string & failure_reason)
{
  wait_if_paused(session_id);
  mark_stage_started(session_id, stage); // 先把“这一步开始跑了”的状态写出去，让界面马上能看到当前进度。

  if (stage.requires_manual_confirmation_before_start ||
      stage.manual_action_type.value != ManualActionType::MANUAL_ACTION_TYPE_UNSPECIFIED) {
    if (!wait_for_manual_confirmation(goal_handle, session_id, stage, failure_reason)) {
      return false;
    }
  }

  StageResultSummary result; // 这一步真正执行完后，要把结果写到这里，再统一交给主控记录。
  result.stage_id = stage.stage_id;      // 先把结果和当前步骤绑定起来。
  result.stage_type = stage.stage_type;  // 带回这一步属于哪类步骤。
  result.module_type = stage.module_type; // 带回这一步属于哪个模块。
  result.final_job_state = make_job_state(JobState::RUNNING);
  result.approval_state.value = calibration_workshop_orchestration_interfaces::msg::ApprovalState::APPROVAL_STATE_UNSPECIFIED;

  if (!dispatch_stage_to_module(session_id, stage, result, failure_reason)) {
    if (failure_reason.empty()) {
      failure_reason = "Stage dispatch failed."; // 如果下层没有给出明确失败原因，这里补一个兜底原因。
    }

    if (result.summary.empty()) {
      result.summary = failure_reason; // 把失败原因同步写进阶段结果，方便后面进报告。
    }
    if (result.final_job_state.state == JobState::JOB_STATE_UNSPECIFIED ||
      result.final_job_state.state == JobState::RUNNING)
    {
      result.final_job_state = make_job_state(JobState::FAILED); // 下层没写状态时，这里明确标成失败。
    }
    result.success = false;
    result.auto_acceptance_passed = false;
    if (result.failure_root_cause.empty()) {
      result.failure_root_cause = failure_reason;
    }
    record_stage_result(session_id, result);
    mark_stage_failed(session_id, stage, failure_reason); // 发一条“这一步失败了”的通知。
    return false;
  }

  if (!result.success || !result.auto_acceptance_passed) {
    if (failure_reason.empty()) {
      failure_reason = result.summary.empty() ? "Stage failed." : result.summary; // 执行器返回失败但没写原因时，这里补齐失败原因。
    }
    if (result.final_job_state.state == JobState::JOB_STATE_UNSPECIFIED ||
      result.final_job_state.state == JobState::RUNNING)
    {
      result.final_job_state = make_job_state(JobState::FAILED);
    }
    if (result.failure_root_cause.empty()) {
      result.failure_root_cause = failure_reason;
    }
    record_stage_result(session_id, result);
    mark_stage_failed(session_id, stage, failure_reason);
    return false;
  }

  if (stage.requires_approval_before_commit) {
    if (!wait_for_stage_approval(goal_handle, session_id, stage, result, failure_reason)) {
      record_stage_result(session_id, result);
      mark_stage_failed(session_id, stage, failure_reason);
      return false;
    }
  } else {
    result.approval_state.value = calibration_workshop_orchestration_interfaces::msg::ApprovalState::APPROVAL_NOT_REQUIRED;
  }

  mark_stage_completed(session_id, stage, result); // 真正成功时，统一走“阶段完成”收尾。
  return true;
}

bool WorkshopOrchestratorV2Node::wait_for_manual_confirmation(
  const std::shared_ptr<GoalHandleExecuteWorkshopSession> goal_handle,
  const std::string & session_id,
  const StagePlan & stage,
  std::string & failure_reason)
{
  std::unique_lock<std::mutex> lock(mutex_);
  auto & record = sessions_.at(session_id);
  record.waiting_stage_id = stage.stage_id;
  record.manual_action_pending = true;
  record.manual_action_confirmed = false;
  record.session.state = make_session_state(WorkshopSessionState::WAITING_MANUAL_ACTION);
  record.session.updated_timestamp_us = now_us();
  publish_event(record, WorkshopEventType::MANUAL_ACTION_REQUIRED, "Manual action required.", stage.stage_id,
    JobState::RUNNING, stage.stage_type.value, stage.module_type.value, true);

  manual_action_cv_.wait(lock, [&]() {
    return !record.manual_action_pending ||
           goal_handle->is_canceling();
  });

  if (goal_handle->is_canceling()) {
    failure_reason.clear();
    return false;
  }

  if (!record.manual_action_confirmed) {
    failure_reason = "Manual action was not confirmed.";
    return false;
  }

  record.waiting_stage_id.clear();
  record.session.state = make_session_state(WorkshopSessionState::RUNNING);
  record.session.updated_timestamp_us = now_us();
  publish_event(record, WorkshopEventType::SESSION_STATE_CHANGED, "Manual action completed.");
  return true;
}

bool WorkshopOrchestratorV2Node::wait_for_stage_approval(
  const std::shared_ptr<GoalHandleExecuteWorkshopSession> goal_handle,
  const std::string & session_id,
  const StagePlan & stage,
  StageResultSummary & result,
  std::string & failure_reason)
{
  std::unique_lock<std::mutex> lock(mutex_);
  auto & record = sessions_.at(session_id);
  record.waiting_stage_id = stage.stage_id;
  record.approval_pending = true;
  record.approval_decided = false;
  record.approval_approved = false;
  record.session.state = make_session_state(WorkshopSessionState::WAITING_APPROVAL);
  record.session.updated_timestamp_us = now_us();
  result.approval_state.value = calibration_workshop_orchestration_interfaces::msg::ApprovalState::APPROVAL_PENDING;
  publish_event(record, WorkshopEventType::APPROVAL_REQUIRED, "Approval required.", stage.stage_id,
    JobState::SUCCEEDED, stage.stage_type.value, stage.module_type.value, false,
    calibration_workshop_orchestration_interfaces::msg::ApprovalState::APPROVAL_PENDING);

  approval_cv_.wait(lock, [&]() {
    return !record.approval_pending ||
           goal_handle->is_canceling();
  });

  if (goal_handle->is_canceling()) {
    failure_reason.clear();
    return false;
  }

  record.waiting_stage_id.clear();
  if (!record.approval_decided || !record.approval_approved) {
    result.approval_state.value = calibration_workshop_orchestration_interfaces::msg::ApprovalState::REJECTED;
    result.success = false;
    result.auto_acceptance_passed = false;
    result.rolled_back = true;
    result.summary = "Stage approval rejected.";
    result.failure_root_cause = result.summary;
    failure_reason = result.summary;
    record.session.state = make_session_state(WorkshopSessionState::FAILED);
    record.session.updated_timestamp_us = now_us();
    publish_event(record, WorkshopEventType::STAGE_FAILED, failure_reason, stage.stage_id,
      JobState::FAILED, stage.stage_type.value, stage.module_type.value);
    return false;
  }

  result.approval_state.value = calibration_workshop_orchestration_interfaces::msg::ApprovalState::APPROVED;
  record.session.state = make_session_state(WorkshopSessionState::RUNNING);
  record.session.updated_timestamp_us = now_us();
  publish_event(record, WorkshopEventType::SESSION_STATE_CHANGED, "Stage approved.", stage.stage_id,
    JobState::SUCCEEDED, stage.stage_type.value, stage.module_type.value, false,
    calibration_workshop_orchestration_interfaces::msg::ApprovalState::APPROVED);
  return true;
}

bool WorkshopOrchestratorV2Node::dispatch_stage_to_module(
  const std::string & session_id,
  const StagePlan & stage,
  StageResultSummary & result,
  std::string & failure_reason)
{
  // 这一层的作用只有一个：根据步骤所属模块，把执行请求分发到正确的模块入口。
  switch (stage.module_type.value) {
    case CalibrationModuleType::EXTERNAL_LOCALIZATION_MODULE:
      return run_external_localization_stage(session_id, stage, result, failure_reason);

    case CalibrationModuleType::CHASSIS_MODULE:
      return run_chassis_stage(session_id, stage, result, failure_reason);

    case CalibrationModuleType::CONTROL_MODULE:
      return run_control_stage(session_id, stage, result, failure_reason);

    case CalibrationModuleType::SENSOR_INTRINSIC_MODULE:
    case CalibrationModuleType::SENSOR_EXTRINSIC_MODULE:
    case CalibrationModuleType::HAND_EYE_MODULE:
      return run_sensor_stage(session_id, stage, result, failure_reason);

    default:
      failure_reason = "Unsupported module type in stage dispatch."; // 如果计划里出现主控还不认识的模块类型，直接在这里拦住。
      result.success = false;
      result.auto_acceptance_passed = false;
      result.final_job_state = make_job_state(JobState::FAILED);
      result.summary = failure_reason;
      return false;
  }
}

bool WorkshopOrchestratorV2Node::run_external_localization_stage(
  const std::string & session_id,
  const StagePlan & stage,
  StageResultSummary & result,
  std::string & failure_reason)
{
  if (!external_localization_client_) {
    failure_reason = "External localization client is not initialized.";
    result.success = false;
    result.auto_acceptance_passed = false;
    result.final_job_state = make_job_state(JobState::FAILED);
    result.summary = failure_reason;
    result.failure_root_cause = failure_reason;
    return false;
  }

  WorkshopSession session_snapshot;
  {
    std::lock_guard<std::mutex> lock(mutex_);
    session_snapshot = sessions_.at(session_id).session; // 先拷一份任务快照，避免拿着主锁去等外部 action。
  }

  // 注意：这里仍然是 orchestrator 在“调 external”。
  // 只是 external 的专项实现细节不再继续长在 orchestrator 里，而是收敛到 external_localization_service。
  return external_localization_client_->execute_reference_check(
    session_snapshot,
    stage,
    result,
    failure_reason);
}

bool WorkshopOrchestratorV2Node::run_chassis_stage(
  const std::string & session_id,
  const StagePlan & stage,
  StageResultSummary & result,
  std::string & failure_reason)
{
  if (!chassis_gateway_client_) {
    failure_reason = "底盘 gateway client 未初始化。";
    result.success = false;
    result.auto_acceptance_passed = false;
    result.final_job_state = make_job_state(JobState::FAILED);
    result.summary = failure_reason;
    result.failure_root_cause = failure_reason;
    return false;
  }

  WorkshopSession session_snapshot;
  {
    std::lock_guard<std::mutex> lock(mutex_);
    session_snapshot = sessions_.at(session_id).session; // 拷贝快照，避免持锁等待 action 结果。
  }

  return chassis_gateway_client_->execute_chassis_stage(
    session_snapshot, stage, result, failure_reason);
}

bool WorkshopOrchestratorV2Node::run_control_stage(
  const std::string & session_id,
  const StagePlan & stage,
  StageResultSummary & result,
  std::string & failure_reason)
{
  if (!control_gateway_client_) {
    failure_reason = "运控 gateway client 未初始化。";
    result.success = false;
    result.auto_acceptance_passed = false;
    result.final_job_state = make_job_state(JobState::FAILED);
    result.summary = failure_reason;
    result.failure_root_cause = failure_reason;
    return false;
  }

  WorkshopSession session_snapshot;
  {
    std::lock_guard<std::mutex> lock(mutex_);
    session_snapshot = sessions_.at(session_id).session; // 拷贝快照，避免持锁等待 action 结果。
  }

  return control_gateway_client_->execute_control_stage(
    session_snapshot, stage, result, failure_reason);
}

bool WorkshopOrchestratorV2Node::run_sensor_stage(
  const std::string & session_id,
  const StagePlan & stage,
  StageResultSummary & result,
  std::string & failure_reason)
{
  if (!sensor_gateway_client_) {
    failure_reason = "传感器 gateway client 未初始化。";
    result.success = false;
    result.auto_acceptance_passed = false;
    result.final_job_state = make_job_state(JobState::FAILED);
    result.summary = failure_reason;
    result.failure_root_cause = failure_reason;
    return false;
  }

  WorkshopSession session_snapshot;
  {
    std::lock_guard<std::mutex> lock(mutex_);
    session_snapshot = sessions_.at(session_id).session; // 拷贝快照，避免持锁等待 action 结果。
  }

  return sensor_gateway_client_->execute_sensor_stage(
    session_snapshot, stage, result, failure_reason);
}

void WorkshopOrchestratorV2Node::mark_stage_failed(
  const std::string & session_id,
  const StagePlan & stage,
  const std::string & failure_reason)
{
  std::lock_guard<std::mutex> lock(mutex_);
  auto & record = sessions_.at(session_id);
  record.session.updated_timestamp_us = now_us();
  publish_event(
    record,
    WorkshopEventType::STAGE_FAILED,
    failure_reason,
    stage.stage_id,
    JobState::FAILED,
    stage.stage_type.value,
    stage.module_type.value,
    false,
    calibration_workshop_orchestration_interfaces::msg::ApprovalState::APPROVAL_STATE_UNSPECIFIED);
}

void WorkshopOrchestratorV2Node::mark_stage_started(const std::string & session_id, const StagePlan & stage)
{
  std::lock_guard<std::mutex> lock(mutex_);
  auto & record = sessions_.at(session_id);
  record.session.active_stage_id = stage.stage_id;
  record.session.updated_timestamp_us = now_us();
  publish_event(
    record,
    WorkshopEventType::STAGE_STARTED,
    "Stage started.",
    stage.stage_id,
    JobState::RUNNING,
    stage.stage_type.value,
    stage.module_type.value,
    false,
    calibration_workshop_orchestration_interfaces::msg::ApprovalState::APPROVAL_STATE_UNSPECIFIED);
}

void WorkshopOrchestratorV2Node::mark_stage_completed(
  const std::string & session_id,
  const StagePlan & stage,
  const StageResultSummary & result)
{
  record_stage_result(session_id, result);
  std::lock_guard<std::mutex> lock(mutex_);
  auto & record = sessions_.at(session_id);
  record.session.updated_timestamp_us = now_us();
  publish_event(
    record,
    WorkshopEventType::STAGE_COMPLETED,
    result.summary.empty() ? "Stage completed." : result.summary,
    stage.stage_id,
    result.final_job_state.state == JobState::JOB_STATE_UNSPECIFIED ? JobState::SUCCEEDED : result.final_job_state.state,
    stage.stage_type.value,
    stage.module_type.value,
    false,
    result.approval_state.value);
}

void WorkshopOrchestratorV2Node::wait_if_paused(const std::string & session_id)
{
  std::unique_lock<std::mutex> lock(mutex_);
  auto & record = sessions_.at(session_id);
  if (!pause_requested_) {
    return;
  }

  record.session.state = make_session_state(WorkshopSessionState::PAUSED);
  record.session.updated_timestamp_us = now_us();
  publish_event(record, WorkshopEventType::SESSION_STATE_CHANGED, "Session paused.");

  pause_cv_.wait(lock, [this]() { return !pause_requested_; });

  record.session.state = make_session_state(WorkshopSessionState::RUNNING);
  record.session.updated_timestamp_us = now_us();
  publish_event(record, WorkshopEventType::SESSION_STATE_CHANGED, "Session resumed.");
}

void WorkshopOrchestratorV2Node::record_stage_result(
  const std::string & session_id,
  const StageResultSummary & result)
{
  std::lock_guard<std::mutex> lock(mutex_);
  auto & record = sessions_.at(session_id);
  bool replaced = false;
  for (auto & existing : record.stage_results) {
    if (existing.stage_id == result.stage_id) {
      existing = result;
      replaced = true;
      break;
    }
  }
  if (!replaced) {
    record.stage_results.push_back(result);
  }
}

void WorkshopOrchestratorV2Node::append_report_trace_files(WorkshopReport & report) const
{
  const auto data_input_ref = make_trace_file_reference(
    data_input_params_file_,
    "本次会话算法数据输入参数文件");
  append_report_file(report, data_input_ref);
  append_metadata_value(report, "data_input_params_file", data_input_ref.file_uri);

  const std::string resolved_dataset_index_file =
    dataset_index_file_.empty() ? infer_dataset_index_file(data_input_params_file_) : dataset_index_file_;
  const auto dataset_index_ref = make_trace_file_reference(
    resolved_dataset_index_file,
    "本次会话真实采集数据集索引文件");
  append_report_file(report, dataset_index_ref);
  append_metadata_value(report, "dataset_index_file", dataset_index_ref.file_uri);
}

void WorkshopOrchestratorV2Node::finish_session_precheck_failed(
  const std::shared_ptr<GoalHandleExecuteWorkshopSession> goal_handle,
  const SessionExecutionContext & context,
  const WorkshopPrecheckResponse & /*precheck*/)
{
  auto result = std::make_shared<ExecuteWorkshopSession::Result>();
  {
    std::lock_guard<std::mutex> lock(mutex_);
    auto & record = sessions_.at(context.session_id);
    record.session.state = make_session_state(WorkshopSessionState::FAILED);
    record.session.active_stage_id.clear();
    record.session.updated_timestamp_us = now_us();
    record.report = report_builder_.build(
      record.session,
      record.stage_results,
      false,
      context.started_timestamp_us,
      now_us(),
      record.last_precheck.message.empty() ? "Precheck failed." : record.last_precheck.message);
    append_report_trace_files(record.report);
    record.executing = false;
    record.report_ready = true;
    publish_event(record, WorkshopEventType::REPORT_READY, "Report ready: precheck failed.");

    result->result.success = false;
    result->result.error_code = make_error_code(ErrorCode::VALIDATION_FAILED);
    result->result.message = record.last_precheck.message.empty() ? "Precheck failed." : record.last_precheck.message;
    result->result.report = record.report;
  }
  goal_handle->abort(result);
}

void WorkshopOrchestratorV2Node::finish_session_failed(
  const std::shared_ptr<GoalHandleExecuteWorkshopSession> goal_handle,
  const SessionExecutionContext & context,
  const std::string & failure_reason)
{
  auto result = std::make_shared<ExecuteWorkshopSession::Result>();
  {
    std::lock_guard<std::mutex> lock(mutex_);
    auto & record = sessions_.at(context.session_id);
    record.session.state = make_session_state(WorkshopSessionState::FAILED);
    record.session.active_stage_id.clear();
    record.session.updated_timestamp_us = now_us();
    record.report = report_builder_.build(
      record.session,
      record.stage_results,
      false,
      context.started_timestamp_us,
      now_us(),
      failure_reason);
    append_report_trace_files(record.report);
    record.executing = false;
    record.report_ready = true;
    publish_event(record, WorkshopEventType::REPORT_READY, "Report ready: session failed.");

    result->result.success = false;
    result->result.error_code = make_error_code(ErrorCode::INVALID_STATE);
    result->result.message = failure_reason;
    result->result.report = record.report;
  }
  goal_handle->abort(result);
}

void WorkshopOrchestratorV2Node::finish_session_succeeded(
  const std::shared_ptr<GoalHandleExecuteWorkshopSession> goal_handle,
  const SessionExecutionContext & context)
{
  auto result = std::make_shared<ExecuteWorkshopSession::Result>();
  {
    std::lock_guard<std::mutex> lock(mutex_);
    auto & record = sessions_.at(context.session_id);
    record.session.active_stage_id.clear();
    record.session.state = make_session_state(WorkshopSessionState::SUCCEEDED);
    record.session.updated_timestamp_us = now_us();
    record.report = report_builder_.build(
      record.session,
      record.stage_results,
      true,
      context.started_timestamp_us,
      now_us(),
      "Workshop v2 minimal flow succeeded.");
    append_report_trace_files(record.report);
    record.executing = false;
    record.report_ready = true;
    publish_event(record, WorkshopEventType::REPORT_READY, "Report ready.");

    result->result.success = true;
    result->result.error_code = make_error_code(ErrorCode::OK);
    result->result.message = "Session succeeded.";
    result->result.report = record.report;
  }
  goal_handle->succeed(result);
}

void WorkshopOrchestratorV2Node::finish_session_canceled(
  const std::shared_ptr<GoalHandleExecuteWorkshopSession> goal_handle,
  const SessionExecutionContext & context)
{
  auto result = std::make_shared<ExecuteWorkshopSession::Result>();
  {
    std::lock_guard<std::mutex> lock(mutex_);
    auto & record = sessions_.at(context.session_id);
    record.session.active_stage_id.clear();
    record.session.state = make_session_state(WorkshopSessionState::CANCELED);
    record.session.updated_timestamp_us = now_us();
    record.report = report_builder_.build(
      record.session,
      record.stage_results,
      false,
      context.started_timestamp_us,
      now_us(),
      "Session canceled.");
    append_report_trace_files(record.report);
    record.executing = false;
    record.report_ready = true;
    publish_event(record, WorkshopEventType::SESSION_STATE_CHANGED, "Session canceled.");

    result->result.success = false;
    result->result.error_code = make_error_code(ErrorCode::ROLLBACK_REQUIRED);
    result->result.message = "Canceled.";
    result->result.report = record.report;
  }
  goal_handle->canceled(result);
}

void WorkshopOrchestratorV2Node::publish_event(
  const SessionRecord & record,
  uint8_t event_type,
  const std::string & message,
  const std::string & stage_id,
  uint8_t stage_job_state,
  uint8_t stage_type,
  uint8_t module_type,
  bool requires_manual_ack,
  uint8_t approval_state)
{
  WorkshopEvent event;
  event.server_timestamp_us = now_us();
  event.session_id = record.session.session_id;
  event.stage_id = stage_id;
  event.event_type.value = event_type;
  event.session_state = record.session.state;
  event.stage_job_state = make_job_state(stage_job_state);
  event.stage_type = make_stage_type(stage_type);
  event.module_type = make_module_type(module_type);
  event.requires_manual_ack = requires_manual_ack;
  event.approval_state.value = approval_state;
  event.message = message;
  event_publisher_->publish(event);
}

std::string WorkshopOrchestratorV2Node::make_session_id() const
{
  static uint64_t counter = 0;
  std::ostringstream oss;
  oss << "workshop_v2_" << now_us() << "_" << ++counter;
  return oss.str();
}

}  // namespace workshop_orchestrator_v2
