#ifndef SMO_INTRIN_THRESHOLD_PARAMS_H
#define SMO_INTRIN_THRESHOLD_PARAMS_H

#include <array>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <stdexcept>
#include <string>
#include <string_view>
#include <utility>
#include <vector>

#include <user/deviceAttachmentSpec.h>

namespace smo::intrin {

enum class ThresholdUnit
{
	Percentage,
	Absolute,
};

struct ParsedThresholdParam
{
	int value;
	ThresholdUnit unit;
	std::string_view matchedName;
	bool wasSpecified;
};

/* Canonical unprefixed threshold-param names that live inside postrin(...) /
 * negtrin(...) segments attached to a nontrin DAP spec. The "-pc" and
 * "-percentage" variants are Percentage-unit; the "-thr", "-thresh" and
 * "-threshold" variants are Absolute-unit.
 */
inline constexpr std::array<std::string_view, 2> kIntrinInterestPcNames = {
	"interest-percentage",
	"interest-pc",
};

inline constexpr std::array<std::string_view, 3> kIntrinInterestThrNames = {
	"interest-threshold",
	"interest-thresh",
	"interest-thr",
};

inline constexpr std::array<std::string_view, 2> kIntrinDistractionPcNames = {
	"distraction-percentage",
	"distraction-pc",
};

inline constexpr std::array<std::string_view, 3> kIntrinDistractionThrNames = {
	"distraction-threshold",
	"distraction-thresh",
	"distraction-thr",
};

inline constexpr std::array<std::string_view, 4> kIntrinStupefactionPcNames = {
	"stupefaction-percentage",
	"stupefaction-pc",
	"stupefying-percentage",
	"stupefying-pc",
};

inline constexpr std::array<std::string_view, 6> kIntrinStupefactionThrNames = {
	"stupefaction-threshold",
	"stupefaction-thresh",
	"stupefaction-thr",
	"stupefying-threshold",
	"stupefying-thresh",
	"stupefying-thr",
};

inline constexpr std::array<std::string_view, 2> kIntrinIntolerablePcNames = {
	"intolerable-percentage",
	"intolerable-pc",
};

inline constexpr std::array<std::string_view, 3> kIntrinIntolerableThrNames = {
	"intolerable-threshold",
	"intolerable-thresh",
	"intolerable-thr",
};

/* Unitless stems are invalid — authors must name the unit via -pc/-percentage
 * or -thr/-thresh/-threshold. We reject bare "interest", "distraction", etc.
 */
inline constexpr std::array<std::string_view, 5> kForbiddenUnitlessIntrinStems = {
	"interest",
	"distraction",
	"stupefaction",
	"stupefying",
	"intolerable",
};

template <std::size_t N>
inline bool arrayContains(
	const std::array<std::string_view, N>& names,
	std::string_view candidate)
{
	return device::DeviceAttachmentSpec::namesContain(names, candidate);
}

inline bool isKnownIntrinThresholdParamName(std::string_view name)
{
	using device::DeviceAttachmentSpec;

	return DeviceAttachmentSpec::namesContain(kIntrinInterestPcNames, name)
		|| DeviceAttachmentSpec::namesContain(kIntrinInterestThrNames, name)
		|| DeviceAttachmentSpec::namesContain(kIntrinDistractionPcNames, name)
		|| DeviceAttachmentSpec::namesContain(kIntrinDistractionThrNames, name)
		|| DeviceAttachmentSpec::namesContain(kIntrinStupefactionPcNames, name)
		|| DeviceAttachmentSpec::namesContain(kIntrinStupefactionThrNames, name)
		|| DeviceAttachmentSpec::namesContain(kIntrinIntolerablePcNames, name)
		|| DeviceAttachmentSpec::namesContain(kIntrinIntolerableThrNames, name);
}

/* Intrin threshold params (interest-*, distraction-*, stupefaction-*,
 * intolerable-*) must appear only inside postrin(...) / negtrin(...) segments
 * on a DAP spec, never on qualeIfaceApi params. The deprecated `from-stimbuff`
 * marker is also rejected — postrin/negtrin are now directly attached to the
 * nontrin stimbuff they trigger from.
 */
inline void validateNoIntrinParamsOnQualeIface(
	const std::string& qualeIfaceApi,
	const std::vector<std::pair<std::string, std::string>>& params)
{
	for (const auto& [name, value] : params)
	{
		(void)value;

		if (isKnownIntrinThresholdParamName(name))
		{
			throw std::runtime_error(
				"Intrinsic threshold param '" + name + "' is not valid on "
				"qualeIfaceApi '" + qualeIfaceApi + "'. Declare it inside a "
				"postrin(...) or negtrin(...) segment attached to this DAP "
				"spec.");
		}

		if (arrayContains(kForbiddenUnitlessIntrinStems, name))
		{
			throw std::runtime_error(
				"Intrinsic threshold param '" + name + "' on qualeIfaceApi '"
				+ qualeIfaceApi + "' is invalid without a unit suffix and "
				"does not belong on qualeIfaceApi params anyway. Use a "
				"postrin(...) or negtrin(...) segment with a '-pc' or "
				"'-thr' suffix.");
		}

		if (name == "from-stimbuff")
		{
			throw std::runtime_error(
				"'from-stimbuff' is no longer supported. postrin(...) and "
				"negtrin(...) are now attached directly to the nontrin DAP "
				"spec they trigger from; remove 'from-stimbuff' from "
				"qualeIfaceApi '" + qualeIfaceApi + "'.");
		}
	}
}

/* Accepts only unprefixed threshold names and the two passband modifiers that
 * are defined on the sensory side. Rejects unit-less stems so that mis-typed
 * params fail loudly.
 */
inline void validateIntrinSegmentParams(
	std::string_view intrinKind,  // "postrin" or "negtrin"
	const std::vector<std::pair<std::string, std::string>>& params)
{
	for (const auto& [name, value] : params)
	{
		(void)value;

		if (arrayContains(kForbiddenUnitlessIntrinStems, name))
		{
			throw std::runtime_error(
				std::string(intrinKind) + "(...) param '" + name + "' is "
				"invalid without a unit suffix. Use '-pc'/'-percentage' or "
				"'-thr'/'-thresh'/'-threshold'.");
		}
	}
}

inline ParsedThresholdParam parseOptionalThresholdParam(
	const std::vector<std::pair<std::string, std::string>>& params,
	const auto& percentageNames,
	const auto& absoluteNames,
	int defaultValue,
	ThresholdUnit defaultUnit)
{
	for (auto paramIt = params.rbegin(); paramIt != params.rend(); ++paramIt)
	{
		const auto& [name, value] = *paramIt;
		ThresholdUnit unit;

		if (device::DeviceAttachmentSpec::namesContain(percentageNames, name))
			{ unit = ThresholdUnit::Percentage; }
		else if (device::DeviceAttachmentSpec::namesContain(absoluteNames, name))
			{ unit = ThresholdUnit::Absolute; }
		else
			{ continue; }

		return ParsedThresholdParam{
			.value = device::DeviceAttachmentSpec::parseParamValueAsInt(
				name, value),
			.unit = unit,
			.matchedName = name,
			.wasSpecified = true,
		};
	}

	return ParsedThresholdParam{
		.value = defaultValue,
		.unit = defaultUnit,
		.matchedName = {},
		.wasSpecified = false,
	};
}

inline uint32_t resolveThresholdValue(
	const ParsedThresholdParam& param,
	size_t percentageBase)
{
	if (param.unit == ThresholdUnit::Percentage) {
		return static_cast<uint32_t>((percentageBase * param.value) / 100);
	}

	return static_cast<uint32_t>(param.value);
}

} // namespace smo::intrin

#endif // SMO_INTRIN_THRESHOLD_PARAMS_H
