#pragma once

#include "blake3.h"
#include <array>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <sys/wait.h>
#include <unistd.h>
#include <vector>

namespace Util {

static void shell_command(const std::string &cmd,
                          const std::string &dir = ".") {
  pid_t pid = fork();
  if (pid < 0) {
    perror("fork failed");
    std::exit(1);
  }

  if (pid == 0) {
    if (chdir(dir.c_str()) != 0) {
      perror("chdir failed");
      _exit(1);
    }

    std::cout << "\n  " << cmd << "\n\n";
    execl("/bin/sh", "sh", "-c", cmd.c_str(), nullptr); // NOLINT(*-vararg)

    perror("execl failed");
    _exit(1);
  } else {
    int status = 0;
    if (waitpid(pid, &status, 0) < 0) {
      perror("waitpid failed");
      std::exit(1);
    }
    if (!WIFEXITED(status) || WEXITSTATUS(status) != 0) {
      std::cerr << "Command failed with exit code " << WEXITSTATUS(status)
                << "\n";
      std::exit(1);
    }
  }
}

static std::string basename(const std::string &path) {
  size_t pos = path.find_last_of("/\\");
  if (pos == std::string::npos) {
    return path;
  }
  return path.substr(pos + 1);
}

static std::string hash_file(const std::string &path) {
  blake3_hasher hasher;
  blake3_hasher_init(&hasher);

  std::ifstream file(path, std::ios::binary);
  if (!file) {
    throw std::runtime_error("failed to open file");
  }

  std::vector<char> buffer(65536);
  while (file.read(buffer.data(), static_cast<long>(buffer.size())) ||
         file.gcount() > 0) {
    blake3_hasher_update(&hasher, buffer.data(), file.gcount());
  }

  std::array<uint8_t, BLAKE3_OUT_LEN> output{};
  blake3_hasher_finalize(&hasher, output.data(), BLAKE3_OUT_LEN);

  std::stringstream ss;
  ss << std::hex << std::setfill('0');
  for (const auto &byte : output) {
    ss << std::setw(2) << static_cast<int>(byte);
  }
  return ss.str();
}

}; // namespace Util
