#ifndef CF_STEP_UTILS_H
#define CF_STEP_UTILS_H

#include <stddef.h>
#include <string.h>

#include "cf_step_abi.h"
#include "cf_vector_f64.h"

#ifdef __cplusplus
extern "C" {
#endif

static inline int cf_handle_is_empty(const CfValueHandle* handle) {
  return (!handle || !handle->data || handle->size == 0);
}

static inline int cf_handle_equals(const CfValueHandle* handle, const char* literal) {
  if (!handle || !handle->data || !literal) return 0;
  size_t len = strlen(literal);
  if (handle->size != len) return 0;
  return memcmp(handle->data, literal, len) == 0;
}

static inline const CfVectorF64* cf_as_vector_f64(const CfValueHandle* handle) {
  if (!handle || handle->storage_type != CF_STORAGE_VECTOR_F64) return NULL;
  return (const CfVectorF64*)handle->data;
}

#ifdef __cplusplus
}
#endif

#ifdef __cplusplus
#include <cmath>
#include <cctype>
#include <cstring>
#include <cstdio>
#include <ctime>
#include <filesystem>
#include <iomanip>
#include <string>
#include <sstream>
#include <cstdlib>
#include <utility>
#include <vector>
#if defined(_WIN32)
#include <windows.h>
#endif

static inline std::string cf_handle_to_string(const CfValueHandle* handle) {
  if (!handle || !handle->data || handle->size == 0) return {};
  return std::string(reinterpret_cast<const char*>(handle->data), handle->size);
}

static inline bool cf_read_i64(const CfValueHandle* handle, int64_t& out) {
  if (!handle || !handle->data || handle->size < sizeof(int64_t)) return false;
  out = *reinterpret_cast<const int64_t*>(handle->data);
  return true;
}

static inline CfStatusCode cf_out_vector_f64(CfCallContext* ctx, CfValueHandle* out, const double* data, size_t length) {
  if (!ctx || !ctx->allocator || !ctx->allocator->alloc || !out) return CF_STATUS_ERROR;
  CfVectorF64* vector = reinterpret_cast<CfVectorF64*>(cf_alloc(ctx->allocator, sizeof(CfVectorF64)));
  if (!vector) return CF_STATUS_ERROR;
  vector->data = nullptr;
  vector->length = length;
  if (length > 0) {
    double* buffer = reinterpret_cast<double*>(cf_alloc(ctx->allocator, sizeof(double) * length));
    if (!buffer) return CF_STATUS_ERROR;
    std::memcpy(buffer, data, sizeof(double) * length);
    vector->data = buffer;
  }
  out->storage_type = CF_STORAGE_VECTOR_F64;
  out->data = vector;
  out->size = sizeof(CfVectorF64);
  return CF_STATUS_OK;
}

static inline std::vector<double> cf_parse_inline_values(const std::string& payload) {
  std::vector<double> values;
  const char* ptr = payload.c_str();
  char* end = nullptr;
  while (*ptr) {
    double v = std::strtod(ptr, &end);
    if (end && end != ptr) {
      values.push_back(v);
      ptr = end;
    } else {
      ++ptr;
    }
  }
  return values;
}

static inline std::vector<std::pair<std::string, std::string>> cf_parse_nodes_json(const std::string& payload) {
  std::vector<std::pair<std::string, std::string>> items;
  const char* p = payload.c_str();
  auto skip_ws = [&]() {
    while (*p && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) ++p;
  };
  auto parse_string = [&]() -> std::string {
    skip_ws();
    if (*p != '"') return {};
    ++p;
    std::string out;
    while (*p && *p != '"') {
      if (*p == '\\') {
        ++p;
        if (!*p) break;
        out.push_back(*p);
        ++p;
      } else {
        out.push_back(*p++);
      }
    }
    if (*p == '"') ++p;
    return out;
  };

  skip_ws();
  if (*p != '{') return items;
  ++p;
  for (;;) {
    skip_ws();
    if (*p == '}') {
      ++p;
      break;
    }
    std::string key = parse_string();
    skip_ws();
    if (*p != ':') break;
    ++p;
    std::string value = parse_string();
    if (!key.empty() && !value.empty()) {
      items.push_back({key, value});
    }
    skip_ws();
    if (*p == ',') {
      ++p;
      continue;
    }
    if (*p == '}') {
      ++p;
      break;
    }
    break;
  }
  return items;
}

static inline bool cf_extract_json_field(const std::string& payload, const std::string& field, std::string& out_value) {
  const char* p = payload.c_str();
  auto skip_ws = [&]() {
    while (*p && (*p == ' ' || *p == '\n' || *p == '\r' || *p == '\t')) ++p;
  };
  auto parse_string = [&]() -> std::string {
    skip_ws();
    if (*p != '"') return {};
    ++p;
    std::string out;
    while (*p && *p != '"') {
      if (*p == '\\') {
        ++p;
        if (!*p) break;
        out.push_back(*p);
        ++p;
      } else {
        out.push_back(*p++);
      }
    }
    if (*p == '"') ++p;
    return out;
  };
  auto parse_value_raw = [&]() -> std::string {
    skip_ws();
    const char* start = p;
    if (*p == '"') {
      ++p;
      while (*p) {
        if (*p == '\\') {
          ++p;
          if (*p) ++p;
          continue;
        }
        if (*p == '"') {
          ++p;
          break;
        }
        ++p;
      }
      return std::string(start, p - start);
    }
    if (*p == '{' || *p == '[') {
      char open = *p;
      char close = open == '{' ? '}' : ']';
      int depth = 0;
      while (*p) {
        if (*p == '"') {
          ++p;
          while (*p) {
            if (*p == '\\') {
              ++p;
              if (*p) ++p;
              continue;
            }
            if (*p == '"') {
              ++p;
              break;
            }
            ++p;
          }
          continue;
        }
        if (*p == open) ++depth;
        if (*p == close) {
          --depth;
          ++p;
          if (depth <= 0) break;
          continue;
        }
        ++p;
      }
      return std::string(start, p - start);
    }
    while (*p && *p != ',' && *p != '}' && *p != ']' &&
           *p != ' ' && *p != '\n' && *p != '\r' && *p != '\t') {
      ++p;
    }
    return std::string(start, p - start);
  };

  skip_ws();
  if (*p != '{') return false;
  ++p;
  for (;;) {
    skip_ws();
    if (*p == '}') {
      ++p;
      break;
    }
    std::string key = parse_string();
    skip_ws();
    if (*p != ':') break;
    ++p;
    std::string value = parse_value_raw();
    if (!key.empty() && key == field) {
      out_value = value;
      return true;
    }
    skip_ws();
    if (*p == ',') {
      ++p;
      continue;
    }
    if (*p == '}') {
      ++p;
      break;
    }
    break;
  }
  return false;
}

static inline std::string cf_trim(const std::string& text) {
  size_t start = 0;
  while (start < text.size() && std::isspace(static_cast<unsigned char>(text[start])) != 0) {
    ++start;
  }
  size_t end = text.size();
  while (end > start && std::isspace(static_cast<unsigned char>(text[end - 1])) != 0) {
    --end;
  }
  return text.substr(start, end - start);
}

static inline std::string cf_last_nonempty_line(const std::string& text) {
  std::istringstream stream(text);
  std::string line;
  std::string last;
  while (std::getline(stream, line)) {
    std::string trimmed = cf_trim(line);
    if (!trimmed.empty()) {
      last = trimmed;
    }
  }
  return last;
}

static inline std::string cf_to_lower_ascii(std::string value) {
  for (char& ch : value) {
    if (ch >= 'A' && ch <= 'Z') {
      ch = static_cast<char>(ch - 'A' + 'a');
    }
  }
  return value;
}

static inline std::string cf_workspace_root_from_base_dir(const std::string& base_dir) {
  std::filesystem::path path(base_dir);
  if (path.filename().empty() && path.has_parent_path()) {
    path = path.parent_path();
  }
  if (cf_to_lower_ascii(path.filename().string()) == "data_hive" && path.has_parent_path()) {
    path = path.parent_path();
  }
  return path.lexically_normal().string();
}

static inline std::string cf_escape_json_string(const std::string& value) {
  std::ostringstream escaped;
  for (char ch : value) {
    switch (ch) {
      case '\\':
        escaped << "\\\\";
        break;
      case '\"':
        escaped << "\\\"";
        break;
      case '\n':
        escaped << "\\n";
        break;
      case '\r':
        escaped << "\\r";
        break;
      case '\t':
        escaped << "\\t";
        break;
      default:
        escaped << ch;
        break;
    }
  }
  return escaped.str();
}

static inline std::string cf_base64_encode(const std::string& input) {
  static constexpr char kAlphabet[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

  std::string output;
  output.reserve(((input.size() + 2) / 3) * 4);

  size_t index = 0;
  while (index + 3 <= input.size()) {
    const uint32_t block =
      (static_cast<uint32_t>(static_cast<unsigned char>(input[index])) << 16) |
      (static_cast<uint32_t>(static_cast<unsigned char>(input[index + 1])) << 8) |
      static_cast<uint32_t>(static_cast<unsigned char>(input[index + 2]));
    output.push_back(kAlphabet[(block >> 18) & 0x3F]);
    output.push_back(kAlphabet[(block >> 12) & 0x3F]);
    output.push_back(kAlphabet[(block >> 6) & 0x3F]);
    output.push_back(kAlphabet[block & 0x3F]);
    index += 3;
  }

  const size_t remaining = input.size() - index;
  if (remaining == 1) {
    const uint32_t block =
      static_cast<uint32_t>(static_cast<unsigned char>(input[index])) << 16;
    output.push_back(kAlphabet[(block >> 18) & 0x3F]);
    output.push_back(kAlphabet[(block >> 12) & 0x3F]);
    output.push_back('=');
    output.push_back('=');
  } else if (remaining == 2) {
    const uint32_t block =
      (static_cast<uint32_t>(static_cast<unsigned char>(input[index])) << 16) |
      (static_cast<uint32_t>(static_cast<unsigned char>(input[index + 1])) << 8);
    output.push_back(kAlphabet[(block >> 18) & 0x3F]);
    output.push_back(kAlphabet[(block >> 12) & 0x3F]);
    output.push_back(kAlphabet[(block >> 6) & 0x3F]);
    output.push_back('=');
  }

  return output;
}

static inline std::string cf_now_iso8601_utc() {
  auto now = std::time(nullptr);
  std::tm tm{};
#ifdef _WIN32
  gmtime_s(&tm, &now);
#else
  gmtime_r(&now, &tm);
#endif
  char buffer[32];
  std::strftime(buffer, sizeof(buffer), "%Y-%m-%dT%H:%M:%SZ", &tm);
  return std::string(buffer);
}

static inline bool cf_extract_json_number(const std::string& payload, const std::string& field, double& out_value) {
  std::string raw;
  if (!cf_extract_json_field(payload, field, raw)) {
    return false;
  }
  raw = cf_trim(raw);
  if (!raw.empty() && raw.front() == '"' && raw.back() == '"' && raw.size() >= 2) {
    raw = raw.substr(1, raw.size() - 2);
  }
  char* end = nullptr;
  const double parsed = std::strtod(raw.c_str(), &end);
  if (!end || end == raw.c_str()) {
    return false;
  }
  out_value = parsed;
  return true;
}

static inline bool cf_parse_numeric_text(std::string raw, double& out_value) {
  raw = cf_trim(raw);
  if (raw.empty()) {
    return false;
  }

  if (raw.size() >= 2 && raw.front() == '"' && raw.back() == '"') {
    std::string unquoted;
    unquoted.reserve(raw.size() - 2);
    for (size_t i = 1; i + 1 < raw.size(); ++i) {
      if (raw[i] == '\\' && i + 2 < raw.size()) {
        ++i;
      }
      unquoted.push_back(raw[i]);
    }
    raw = cf_trim(unquoted);
    if (raw.empty()) {
      return false;
    }
  }

  char* end = nullptr;
  const double parsed = std::strtod(raw.c_str(), &end);
  if (!end || end == raw.c_str()) {
    return false;
  }
  while (*end != '\0' && std::isspace(static_cast<unsigned char>(*end)) != 0) {
    ++end;
  }
  if (*end != '\0') {
    return false;
  }
  out_value = parsed;
  return true;
}

static inline bool cf_try_read_numeric_scalar(const CfValueHandle* handle, double& out_value) {
  if (!handle) {
    return false;
  }
  if (handle->storage_type == CF_STORAGE_F64 && handle->data && handle->size >= sizeof(double)) {
    out_value = *reinterpret_cast<const double*>(handle->data);
    return true;
  }
  if (handle->storage_type == CF_STORAGE_I64 && handle->data && handle->size >= sizeof(int64_t)) {
    out_value = static_cast<double>(*reinterpret_cast<const int64_t*>(handle->data));
    return true;
  }
  if (!handle->data || handle->size == 0) {
    return false;
  }
  if (handle->storage_type == CF_STORAGE_JSON ||
      handle->storage_type == CF_STORAGE_STRING ||
      handle->storage_type == CF_STORAGE_BYTES) {
    return cf_parse_numeric_text(
      std::string(reinterpret_cast<const char*>(handle->data), handle->size),
      out_value);
  }
  return false;
}

static inline bool cf_extract_json_text(const std::string& payload, const std::string& field, std::string& out_text) {
  std::string raw;
  if (!cf_extract_json_field(payload, field, raw)) {
    return false;
  }
  raw = cf_trim(raw);
  if (raw.size() >= 2 && raw.front() == '"' && raw.back() == '"') {
    raw = raw.substr(1, raw.size() - 2);
  }
  out_text = raw;
  return !out_text.empty();
}

static inline std::string cf_make_session_key(const std::string& scope, const std::string& id) {
  return scope + "|" + id;
}

static inline std::string cf_build_measurement_row_json(
  const std::string& payload,
  int cycle_id,
  const std::string& note) {
  double value = 0.0;
  if (!cf_extract_json_number(payload, "mean_pH", value)) {
    (void)cf_extract_json_number(payload, "value", value);
  }

  std::string ts_utc;
  if (!cf_extract_json_text(payload, "timestamp", ts_utc)) {
    if (!cf_extract_json_text(payload, "ts_utc", ts_utc)) {
      ts_utc = cf_now_iso8601_utc();
    }
  }

  std::ostringstream row;
  row << "{\"cycle_id\":" << cycle_id
      << ",\"value\":" << std::setprecision(17) << value
      << ",\"ts_utc\":\"" << cf_escape_json_string(ts_utc) << "\""
      << ",\"note\":\"" << cf_escape_json_string(note) << "\"}";
  return row.str();
}

static inline std::string cf_getenv_or_default(const char* env_name, const std::string& fallback) {
  if (!env_name || *env_name == '\0') {
    return fallback;
  }
  const char* env_value = std::getenv(env_name);
  if (env_value && *env_value) {
    return std::string(env_value);
  }
  return fallback;
}

static inline std::string cf_find_repo_relative_path(const std::string& relative_path) {
  const std::filesystem::path relative(relative_path);
  std::error_code ec;
  const auto cwd = std::filesystem::current_path(ec);
  if (!ec) {
    auto probe = cwd;
    while (true) {
      const auto candidate = probe / relative;
      if (std::filesystem::exists(candidate, ec) && !ec) {
        return candidate.lexically_normal().string();
      }
      if (!probe.has_parent_path()) {
        break;
      }
      const auto parent = probe.parent_path();
      if (parent == probe) {
        break;
      }
      probe = parent;
    }
  }
  return relative.lexically_normal().string();
}

static inline std::string cf_shell_quote_arg(const std::string& arg) {
#if defined(_WIN32)
  std::string quoted = "\"";
  size_t backslashes = 0;
  for (char ch : arg) {
    if (ch == '\\') {
      ++backslashes;
      continue;
    }
    if (ch == '"') {
      quoted.append(backslashes * 2 + 1, '\\');
      quoted.push_back('"');
      backslashes = 0;
      continue;
    }
    if (backslashes > 0) {
      quoted.append(backslashes, '\\');
      backslashes = 0;
    }
    quoted.push_back(ch);
  }
  if (backslashes > 0) {
    quoted.append(backslashes * 2, '\\');
  }
  quoted.push_back('"');
  return quoted;
#else
  std::string quoted = "'";
  for (char ch : arg) {
    if (ch == '\'') {
      quoted += "'\\''";
    } else {
      quoted.push_back(ch);
    }
  }
  quoted += "'";
  return quoted;
#endif
}

static inline bool cf_run_command_capture(const std::string& command, std::string& out_text) {
  out_text.clear();
#if defined(_WIN32)
  SECURITY_ATTRIBUTES sa{};
  sa.nLength = sizeof(sa);
  sa.lpSecurityDescriptor = nullptr;
  sa.bInheritHandle = TRUE;

  HANDLE read_pipe = nullptr;
  HANDLE write_pipe = nullptr;
  if (!CreatePipe(&read_pipe, &write_pipe, &sa, 0)) {
    return false;
  }
  if (!SetHandleInformation(read_pipe, HANDLE_FLAG_INHERIT, 0)) {
    CloseHandle(read_pipe);
    CloseHandle(write_pipe);
    return false;
  }

  STARTUPINFOA si{};
  si.cb = sizeof(si);
  si.dwFlags = STARTF_USESTDHANDLES;
  si.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
  si.hStdOutput = write_pipe;
  si.hStdError = write_pipe;

  PROCESS_INFORMATION pi{};
  std::vector<char> command_line(command.begin(), command.end());
  command_line.push_back('\0');

  const BOOL started = CreateProcessA(
    nullptr,
    command_line.data(),
    nullptr,
    nullptr,
    TRUE,
    CREATE_NO_WINDOW,
    nullptr,
    nullptr,
    &si,
    &pi);

  CloseHandle(write_pipe);
  if (!started) {
    CloseHandle(read_pipe);
    return false;
  }

  char buffer[512];
  DWORD bytes_read = 0;
  while (ReadFile(read_pipe, buffer, sizeof(buffer), &bytes_read, nullptr) != FALSE && bytes_read > 0) {
    out_text.append(buffer, buffer + bytes_read);
  }
  CloseHandle(read_pipe);

  WaitForSingleObject(pi.hProcess, INFINITE);
  DWORD exit_code = 1;
  GetExitCodeProcess(pi.hProcess, &exit_code);
  CloseHandle(pi.hThread);
  CloseHandle(pi.hProcess);
  return exit_code == 0;
#else
  FILE* pipe = popen(command.c_str(), "r");
  if (!pipe) {
    return false;
  }

  char buffer[512];
  while (std::fgets(buffer, sizeof(buffer), pipe) != nullptr) {
    out_text += buffer;
  }
  const int exit_code = pclose(pipe);
  return exit_code == 0;
#endif
}

static inline bool cf_run_python_script_capture_last_nonempty_line(
  const std::string& python_executable,
  const std::string& script_path,
  const std::vector<std::string>& script_args,
  std::string& out_last_line) {
  std::vector<std::string> args;
  args.reserve(script_args.size() + 2);
  args.push_back(python_executable);
  args.push_back(script_path);
  std::ostringstream debug_command;
  debug_command << cf_shell_quote_arg(python_executable) << " " << cf_shell_quote_arg(script_path);
  for (const std::string& arg : script_args) {
    args.push_back(arg);
    debug_command << " " << cf_shell_quote_arg(arg);
  }
  const char* debug_cmd = std::getenv("CF_STEP_UTILS_DEBUG_COMMAND");
  if (debug_cmd && *debug_cmd) {
    std::fprintf(stderr, "[cf_step_utils] command=%s\n", debug_command.str().c_str());
  }

  std::ostringstream command;
  command << cf_shell_quote_arg(args.front());
  for (size_t index = 1; index < args.size(); ++index) {
    command << " " << cf_shell_quote_arg(args[index]);
  }

  std::string output;
  if (!cf_run_command_capture(command.str(), output)) {
    return false;
  }
  out_last_line = cf_last_nonempty_line(output);
  return !out_last_line.empty();
}

static inline time_t cf_timegm_utc(std::tm* tm) {
#ifdef _WIN32
  return _mkgmtime(tm);
#else
  return timegm(tm);
#endif
}

static inline bool cf_parse_iso8601_to_epoch(const std::string& input, double& out_epoch) {
  std::string s = input;
  if (s.size() >= 2 && s.front() == '"' && s.back() == '"') {
    s = s.substr(1, s.size() - 2);
  }
  if (s.size() < 19) return false;
  if (s[4] != '-' || s[7] != '-' || (s[10] != 'T' && s[10] != 't') || s[13] != ':' || s[16] != ':') {
    return false;
  }
  int year = std::stoi(s.substr(0, 4));
  int month = std::stoi(s.substr(5, 2));
  int day = std::stoi(s.substr(8, 2));
  int hour = std::stoi(s.substr(11, 2));
  int minute = std::stoi(s.substr(14, 2));
  int sec = std::stoi(s.substr(17, 2));

  size_t pos = 19;
  double frac = 0.0;
  if (pos < s.size() && s[pos] == '.') {
    size_t start = ++pos;
    while (pos < s.size() && s[pos] >= '0' && s[pos] <= '9') {
      ++pos;
    }
    if (pos > start) {
      std::string frac_str = s.substr(start, pos - start);
      double scale = 1.0;
      for (size_t i = 0; i < frac_str.size(); ++i) scale *= 0.1;
      frac = std::stod(frac_str) * scale;
    }
  }

  int offset_sec = 0;
  if (pos < s.size()) {
    char tz = s[pos];
    if (tz == 'Z' || tz == 'z') {
      ++pos;
    } else if (tz == '+' || tz == '-') {
      if (pos + 5 >= s.size()) return false;
      int off_h = std::stoi(s.substr(pos + 1, 2));
      int off_m = std::stoi(s.substr(pos + 4, 2));
      offset_sec = off_h * 3600 + off_m * 60;
      if (tz == '-') offset_sec = -offset_sec;
      pos += 6;
    }
  }

  std::tm tm{};
  tm.tm_year = year - 1900;
  tm.tm_mon = month - 1;
  tm.tm_mday = day;
  tm.tm_hour = hour;
  tm.tm_min = minute;
  tm.tm_sec = sec;
  time_t t = cf_timegm_utc(&tm);
  if (t == static_cast<time_t>(-1)) return false;
  double epoch = static_cast<double>(t) + frac;
  if (offset_sec != 0) {
    epoch -= static_cast<double>(offset_sec);
  }
  out_epoch = epoch;
  return true;
}

static inline std::string cf_format_iso8601(double epoch) {
  if (!std::isfinite(epoch)) return {};
  time_t secs = static_cast<time_t>(epoch);
  double frac = epoch - static_cast<double>(secs);
  if (frac < 0) {
    frac = 0;
  }
  std::tm tm{};
#ifdef _WIN32
  gmtime_s(&tm, &secs);
#else
  gmtime_r(&secs, &tm);
#endif
  char buf[32];
  std::strftime(buf, sizeof(buf), "%Y-%m-%dT%H:%M:%S", &tm);
  int millis = static_cast<int>(frac * 1000.0 + 0.5);
  std::ostringstream out;
  out << buf << "." << std::setfill('0') << std::setw(3) << millis << "Z";
  return out.str();
}
#endif

#endif


