#pragma once

// std
#include <fstream>
#include <utility>

// external
#include <toml.hpp>

// internal
#include "poac/core/resolver/resolve.hpp"
#include "poac/data/manifest.hpp"
#include "poac/util/result.hpp"
#include "poac/util/rustify.hpp"

namespace poac::data::lockfile {

inline constexpr StringRef LOCKFILE_NAME = "poac.lock";
inline constexpr StringRef LOCKFILE_HEADER =
    " This file is automatically generated by Poac.\n"
    "# It is not intended for manual editing.";

using InvalidLockfileVersion = Error<"invalid lockfile version found: {}", i64>;
using FailedToReadLockfile = Error<"failed to read lockfile:\n{}", String>;

inline Fn poac_lock_last_modified(const Path& base_dir)->fs::file_time_type {
  return fs::last_write_time(base_dir / LOCKFILE_NAME);
}

inline Fn is_outdated(const Path& base_dir)->bool {
  if (!fs::exists(base_dir / LOCKFILE_NAME)) {
    return true;
  }
  return poac_lock_last_modified(base_dir)
         < manifest::poac_toml_last_modified(base_dir);
}

} // namespace poac::data::lockfile

namespace poac::data::lockfile::inline v1 {

namespace resolver = core::resolver::resolve;

inline constexpr i64 LOCKFILE_VERSION = 1;

// NOLINTNEXTLINE(bugprone-exception-escape)
struct Package {
  String name;
  String version;
  Vec<String> dependencies;
};

struct Lockfile {
  i64 version = LOCKFILE_VERSION;
  Vec<Package> package;
};

// clang-format off
// to avoid reporting errors with inline namespace on only the dry-run mode. (IDK why)
} // namespace poac::data::lockfile::v1
// clang-format on

// NOLINTNEXTLINE(modernize-use-trailing-return-type)
TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(
    poac::data::lockfile::v1::Package, name, version, dependencies
)
// NOLINTNEXTLINE(modernize-use-trailing-return-type)
TOML11_DEFINE_CONVERSION_NON_INTRUSIVE(
    poac::data::lockfile::v1::Lockfile, version, package
)

namespace poac::data::lockfile::inline v1 {

// -------------------- INTO LOCKFILE --------------------

[[nodiscard]] Fn
convert_to_lock(const resolver::UniqDeps<resolver::WithDeps>& deps)
    ->Result<toml::basic_value<toml::preserve_comments>>;

[[nodiscard]] Fn overwrite(const resolver::UniqDeps<resolver::WithDeps>& deps)
    ->Result<void>;

[[nodiscard]] Fn generate(const resolver::UniqDeps<resolver::WithDeps>& deps)
    ->Result<void>;

// -------------------- FROM LOCKFILE --------------------

[[nodiscard]] Fn convert_to_deps(const Lockfile& lock)
    ->resolver::UniqDeps<resolver::WithDeps>;

[[nodiscard]] Fn read(const Path& base_dir)
    ->Result<Option<resolver::UniqDeps<resolver::WithDeps>>>;

// clang-format off
// to avoid reporting errors with inline namespace on only the dry-run mode. (IDK why)
} // namespace poac::data::lockfile::v1
// clang-format on
