/*
 * Copyright 2016 - 2025 Gary Talent (gary@drinkingtea.net). All rights reserved.
 */

#pragma once

#include <ox/claw/claw.hpp>
#include <ox/fs/fs.hpp>
#include <ox/model/typenamecatcher.hpp>
#include <ox/std/buffer.hpp>
#include <ox/std/defines.hpp>
#include <ox/std/fmt.hpp>
#include <ox/std/string.hpp>
#include <ox/std/trace.hpp>

#include <keel/context.hpp>

namespace studio {

namespace detail {
inline ox::String slashesToPct(ox::StringViewCR str) noexcept {
	auto out = ox::String{str};
	for (auto&c: out) {
		if (c == '/' || c == '\\') {
			c = '%';
		}
	}
	return out;
}
}

[[nodiscard]]
ox::String configPath(keel::Context const &kctx) noexcept;

template<typename T>
ox::Result<T> readConfig(keel::Context &kctx, ox::StringViewCR name) noexcept {
	oxAssert(name != "", "Config type has no TypeName");
	auto const path = ox::sfmt("/{}.json", detail::slashesToPct(name));
	ox::PassThroughFS fs(configPath(kctx));
	auto const [buff, err] = fs.read(path);
	if (err) {
		//oxErrf("Could not read config file: {} - {}\n", path, toStr(err));
		return err;
	}
	OX_REQUIRE(id, ox::readClawTypeId(buff));
	ox::Result<T> out;
	if (id != ox::ModelTypeId_v<T>) {
		out = keel::convert<T>(kctx, buff);
	} else {
		out = ox::readClaw<T>(buff);
	}
	OX_RETURN_ERROR(out);
	OX_RETURN_ERROR(keel::ensureValid(out.value));
	return out;
}

template<typename T>
ox::Result<T> readConfig(keel::Context &kctx) noexcept {
	constexpr auto TypeName = ox::requireModelTypeName<T>();
	return readConfig<T>(kctx, TypeName);
}

template<typename T>
ox::Error writeConfig(keel::Context &kctx, ox::StringViewCR name, T const &data) noexcept {
	oxAssert(name != "", "Config type has no TypeName");
	auto const path = ox::sfmt("/{}.json", detail::slashesToPct(name));
	ox::PassThroughFS fs(configPath(kctx));
	if (auto const err = fs.mkdir("/", true)) {
		//oxErrf("Could not create config directory: {} - {}\n", path, toStr(err));
		return err;
	}
	OX_REQUIRE_M(buff, ox::writeClaw(data, ox::ClawFormat::Organic));
	*buff.back().value = '\n';
	if (auto const err = fs.write(path, buff.data(), buff.size())) {
		//oxErrf("Could not read config file: {} - {}\n", path, toStr(err));
		return ox::Error{2, "Could not read config file"};
	}
	return {};
}

template<typename T>
ox::Error writeConfig(keel::Context &kctx, T const &data) noexcept {
	constexpr auto TypeName = ox::requireModelTypeName<T>();
	return writeConfig(kctx, TypeName, data);
}

template<typename T, typename Func>
void openConfig(keel::Context &kctx, ox::StringViewCR name, Func f) noexcept {
	oxAssert(name != "", "Config type has no TypeName");
	auto const [c, err] = readConfig<T>(kctx, name);
	oxLogError(err);
	f(c);
}

template<typename T, typename Func>
void openConfig(keel::Context &kctx, Func f) noexcept {
	constexpr auto TypeName = ox::requireModelTypeName<T>();
	openConfig<T>(kctx, TypeName, f);
}

template<typename T, typename Func>
void editConfig(keel::Context &kctx, ox::StringViewCR name, Func f) noexcept {
	oxAssert(name !=  "", "Config type has no TypeName");
	auto [c, err] = readConfig<T>(kctx, name);
	oxLogError(err);
	f(c);
	oxLogError(writeConfig(kctx, name, c));
}

template<typename T, typename Func>
void editConfig(keel::Context &kctx, Func f) noexcept {
	constexpr auto TypeName = ox::requireModelTypeName<T>();
	editConfig<T>(kctx, TypeName, f);
}

/**
 * Older config files didn't use ClawHeaders, so they can't
 * use the normal conversion system.
 * Functions like this shouldn't be necessary moving forward.
 */
template<typename T>
ox::Error headerizeConfigFile(keel::Context &kctx, ox::StringViewCR name = ox::ModelTypeName_v<T>) noexcept {
	auto const path = ox::sfmt("/{}.json", detail::slashesToPct(name));
	ox::PassThroughFS fs(configPath(kctx));
	OX_REQUIRE_M(buff, fs.read(path));
	OX_REQUIRE_M(cv1, ox::readOC<T>(buff));
	OX_RETURN_ERROR(ox::writeClaw(cv1, ox::ClawFormat::Organic).moveTo(buff));
	return fs.write(path, buff);
}

}
