#include "stdafx.h"
#include "ShaderPackageRegistry.h"

#include "RuntimeJson.h"

#include <algorithm>
#include <cmath>
#include <cctype>
#include <fstream>
#include <sstream>

namespace
{
std::string Trim(const std::string& text)
{
	std::size_t start = 0;
	while (start < text.size() && std::isspace(static_cast<unsigned char>(text[start])))
		++start;

	std::size_t end = text.size();
	while (end > start && std::isspace(static_cast<unsigned char>(text[end - 1])))
		--end;

	return text.substr(start, end - start);
}

bool IsFiniteNumber(double value)
{
	return std::isfinite(value) != 0;
}

bool ParseShaderParameterType(const std::string& typeName, ShaderParameterType& type)
{
	if (typeName == "float")
	{
		type = ShaderParameterType::Float;
		return true;
	}
	if (typeName == "vec2")
	{
		type = ShaderParameterType::Vec2;
		return true;
	}
	if (typeName == "color")
	{
		type = ShaderParameterType::Color;
		return true;
	}
	if (typeName == "bool")
	{
		type = ShaderParameterType::Boolean;
		return true;
	}
	if (typeName == "enum")
	{
		type = ShaderParameterType::Enum;
		return true;
	}
	if (typeName == "text")
	{
		type = ShaderParameterType::Text;
		return true;
	}
	if (typeName == "trigger")
	{
		type = ShaderParameterType::Trigger;
		return true;
	}
	return false;
}

bool ParseTemporalHistorySource(const std::string& sourceName, TemporalHistorySource& source)
{
	if (sourceName == "source")
	{
		source = TemporalHistorySource::Source;
		return true;
	}
	if (sourceName == "preLayerInput")
	{
		source = TemporalHistorySource::PreLayerInput;
		return true;
	}
	if (sourceName == "none")
	{
		source = TemporalHistorySource::None;
		return true;
	}
	return false;
}

std::string ReadTextFile(const std::filesystem::path& path, std::string& error)
{
	std::ifstream input(path, std::ios::binary);
	if (!input)
	{
		error = "Could not open file: " + path.string();
		return std::string();
	}

	std::ostringstream buffer;
	buffer << input.rdbuf();
	return buffer.str();
}

std::string ManifestPathMessage(const std::filesystem::path& manifestPath)
{
	return manifestPath.string();
}

bool RequireStringField(const JsonValue& object, const char* fieldName, std::string& value, const std::filesystem::path& manifestPath, std::string& error)
{
	const JsonValue* fieldValue = object.find(fieldName);
	if (!fieldValue || !fieldValue->isString())
	{
		error = "Shader manifest is missing required string '" + std::string(fieldName) + "' field: " + ManifestPathMessage(manifestPath);
		return false;
	}

	value = fieldValue->asString();
	return true;
}

bool RequireNonEmptyStringField(const JsonValue& object, const char* fieldName, std::string& value, const std::filesystem::path& manifestPath, std::string& error)
{
	if (!RequireStringField(object, fieldName, value, manifestPath, error))
		return false;
	if (Trim(value).empty())
	{
		error = "Shader manifest string '" + std::string(fieldName) + "' must not be empty: " + ManifestPathMessage(manifestPath);
		return false;
	}
	return true;
}

bool OptionalStringField(const JsonValue& object, const char* fieldName, std::string& value, const std::string& fallback, const std::filesystem::path& manifestPath, std::string& error)
{
	const JsonValue* fieldValue = object.find(fieldName);
	if (!fieldValue)
	{
		value = fallback;
		return true;
	}
	if (!fieldValue->isString())
	{
		error = "Shader manifest field '" + std::string(fieldName) + "' must be a string in: " + ManifestPathMessage(manifestPath);
		return false;
	}
	value = fieldValue->asString();
	return true;
}

bool OptionalArrayField(const JsonValue& object, const char* fieldName, const JsonValue*& value, const std::filesystem::path& manifestPath, std::string& error)
{
	value = object.find(fieldName);
	if (!value)
		return true;
	if (!value->isArray())
	{
		error = "Shader manifest '" + std::string(fieldName) + "' field must be an array in: " + ManifestPathMessage(manifestPath);
		return false;
	}
	return true;
}

bool OptionalObjectField(const JsonValue& object, const char* fieldName, const JsonValue*& value, const std::filesystem::path& manifestPath, std::string& error)
{
	value = object.find(fieldName);
	if (!value)
		return true;
	if (!value->isObject())
	{
		error = "Shader manifest '" + std::string(fieldName) + "' field must be an object in: " + ManifestPathMessage(manifestPath);
		return false;
	}
	return true;
}

bool NumberListFromJsonValue(const JsonValue& value, std::vector<double>& numbers, const std::string& fieldName, const std::filesystem::path& manifestPath, std::string& error)
{
	if (value.isNumber())
	{
		numbers.push_back(value.asNumber());
		return true;
	}
	if (value.isArray())
	{
		numbers = JsonArrayToNumbers(value);
		if (numbers.size() != value.asArray().size())
		{
			error = "Shader parameter field '" + fieldName + "' must contain only numbers in: " + ManifestPathMessage(manifestPath);
			return false;
		}
		return true;
	}

	error = "Shader parameter field '" + fieldName + "' must be a number or array of numbers in: " + ManifestPathMessage(manifestPath);
	return false;
}

bool ValidateShaderIdentifier(const std::string& identifier, const std::string& fieldName, const std::filesystem::path& manifestPath, std::string& error)
{
	if (identifier.empty() || !(std::isalpha(static_cast<unsigned char>(identifier.front())) || identifier.front() == '_'))
	{
		error = "Shader manifest field '" + fieldName + "' must be a valid shader identifier in: " + ManifestPathMessage(manifestPath);
		return false;
	}

	for (char ch : identifier)
	{
		const unsigned char unsignedCh = static_cast<unsigned char>(ch);
		if (!(std::isalnum(unsignedCh) || ch == '_'))
		{
			error = "Shader manifest field '" + fieldName + "' must be a valid shader identifier in: " + ManifestPathMessage(manifestPath);
			return false;
		}
	}

	return true;
}

bool ParseShaderMetadata(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
{
	if (!RequireStringField(manifestJson, "id", shaderPackage.id, manifestPath, error) ||
		!RequireStringField(manifestJson, "name", shaderPackage.displayName, manifestPath, error) ||
		!OptionalStringField(manifestJson, "description", shaderPackage.description, "", manifestPath, error) ||
		!OptionalStringField(manifestJson, "category", shaderPackage.category, "", manifestPath, error) ||
		!OptionalStringField(manifestJson, "entryPoint", shaderPackage.entryPoint, "shadeVideo", manifestPath, error))
	{
		return false;
	}

	if (!ValidateShaderIdentifier(shaderPackage.entryPoint, "entryPoint", manifestPath, error))
		return false;

	shaderPackage.directoryPath = manifestPath.parent_path();
	shaderPackage.shaderPath = shaderPackage.directoryPath / "shader.slang";
	shaderPackage.manifestPath = manifestPath;
	return true;
}

bool ParsePassDefinitions(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
{
	const JsonValue* passesValue = nullptr;
	if (!OptionalArrayField(manifestJson, "passes", passesValue, manifestPath, error))
		return false;

	if (!passesValue)
	{
		// Existing shader packages are treated as a single implicit pass, so
		// multipass support does not require manifest churn.
		ShaderPassDefinition pass;
		pass.id = "main";
		pass.entryPoint = shaderPackage.entryPoint;
		pass.sourcePath = shaderPackage.shaderPath;
		pass.outputName = "layerOutput";
		if (!std::filesystem::exists(pass.sourcePath))
		{
			error = "Shader source not found for package " + shaderPackage.id + ": " + pass.sourcePath.string();
			return false;
		}
		pass.sourceWriteTime = std::filesystem::last_write_time(pass.sourcePath);
		shaderPackage.passes.push_back(pass);
		return true;
	}

	if (passesValue->asArray().empty())
	{
		error = "Shader manifest 'passes' field must not be empty in: " + ManifestPathMessage(manifestPath);
		return false;
	}

	for (const JsonValue& passJson : passesValue->asArray())
	{
		if (!passJson.isObject())
		{
			error = "Shader pass entry must be an object in: " + ManifestPathMessage(manifestPath);
			return false;
		}

		std::string passId;
		std::string sourcePath;
		if (!RequireNonEmptyStringField(passJson, "id", passId, manifestPath, error) ||
			!RequireNonEmptyStringField(passJson, "source", sourcePath, manifestPath, error))
		{
			error = "Shader pass is missing required 'id' or 'source' in: " + ManifestPathMessage(manifestPath);
			return false;
		}
		if (!ValidateShaderIdentifier(passId, "passes[].id", manifestPath, error))
			return false;

		for (const ShaderPassDefinition& existingPass : shaderPackage.passes)
		{
			if (existingPass.id == passId)
			{
				error = "Duplicate shader pass id '" + passId + "' in: " + ManifestPathMessage(manifestPath);
				return false;
			}
		}

		ShaderPassDefinition pass;
		pass.id = passId;
		pass.sourcePath = shaderPackage.directoryPath / sourcePath;
		if (!OptionalStringField(passJson, "entryPoint", pass.entryPoint, shaderPackage.entryPoint, manifestPath, error) ||
			!OptionalStringField(passJson, "output", pass.outputName, passId, manifestPath, error))
		{
			return false;
		}
		if (!ValidateShaderIdentifier(pass.entryPoint, "passes[].entryPoint", manifestPath, error))
			return false;

		const JsonValue* inputsValue = nullptr;
		if (!OptionalArrayField(passJson, "inputs", inputsValue, manifestPath, error))
			return false;
		if (inputsValue)
		{
			for (const JsonValue& inputValue : inputsValue->asArray())
			{
				if (!inputValue.isString())
				{
					error = "Shader pass inputs must be strings in: " + ManifestPathMessage(manifestPath);
					return false;
				}
				pass.inputNames.push_back(inputValue.asString());
			}
		}

		// Keep source validation in the registry. Bad pass declarations then
		// appear as unavailable shaders instead of failing at render time.
		if (!std::filesystem::exists(pass.sourcePath))
		{
			error = "Shader pass source not found for package " + shaderPackage.id + ": " + pass.sourcePath.string();
			return false;
		}
		pass.sourceWriteTime = std::filesystem::last_write_time(pass.sourcePath);
		shaderPackage.passes.push_back(pass);
	}

	shaderPackage.shaderPath = shaderPackage.passes.front().sourcePath;
	return true;
}

bool ParseTextureAssets(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
{
	const JsonValue* texturesValue = nullptr;
	if (!OptionalArrayField(manifestJson, "textures", texturesValue, manifestPath, error))
		return false;
	if (!texturesValue)
		return true;

	for (const JsonValue& textureJson : texturesValue->asArray())
	{
		if (!textureJson.isObject())
		{
			error = "Shader texture entry must be an object in: " + ManifestPathMessage(manifestPath);
			return false;
		}

		std::string textureId;
		std::string texturePath;
		if (!RequireNonEmptyStringField(textureJson, "id", textureId, manifestPath, error) ||
			!RequireNonEmptyStringField(textureJson, "path", texturePath, manifestPath, error))
		{
			error = "Shader texture is missing required 'id' or 'path' in: " + ManifestPathMessage(manifestPath);
			return false;
		}
		if (!ValidateShaderIdentifier(textureId, "textures[].id", manifestPath, error))
			return false;

		ShaderTextureAsset textureAsset;
		textureAsset.id = textureId;
		textureAsset.path = shaderPackage.directoryPath / texturePath;
		if (!std::filesystem::exists(textureAsset.path))
		{
			error = "Shader texture asset not found for package " + shaderPackage.id + ": " + textureAsset.path.string();
			return false;
		}

		textureAsset.writeTime = std::filesystem::last_write_time(textureAsset.path);
		shaderPackage.textureAssets.push_back(textureAsset);
	}

	return true;
}

bool ParseFontAssets(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
{
	const JsonValue* fontsValue = nullptr;
	if (!OptionalArrayField(manifestJson, "fonts", fontsValue, manifestPath, error))
		return false;
	if (!fontsValue)
		return true;

	for (const JsonValue& fontJson : fontsValue->asArray())
	{
		if (!fontJson.isObject())
		{
			error = "Shader font entry must be an object in: " + ManifestPathMessage(manifestPath);
			return false;
		}

		std::string fontId;
		std::string fontPath;
		if (!RequireNonEmptyStringField(fontJson, "id", fontId, manifestPath, error) ||
			!RequireNonEmptyStringField(fontJson, "path", fontPath, manifestPath, error))
		{
			error = "Shader font is missing required 'id' or 'path' in: " + ManifestPathMessage(manifestPath);
			return false;
		}
		if (!ValidateShaderIdentifier(fontId, "fonts[].id", manifestPath, error))
			return false;

		ShaderFontAsset fontAsset;
		fontAsset.id = fontId;
		fontAsset.path = shaderPackage.directoryPath / fontPath;
		if (!std::filesystem::exists(fontAsset.path))
		{
			error = "Shader font asset not found for package " + shaderPackage.id + ": " + fontAsset.path.string();
			return false;
		}

		fontAsset.writeTime = std::filesystem::last_write_time(fontAsset.path);
		shaderPackage.fontAssets.push_back(fontAsset);
	}

	return true;
}

bool ParseTemporalSettings(const JsonValue& manifestJson, ShaderPackage& shaderPackage, unsigned maxTemporalHistoryFrames, const std::filesystem::path& manifestPath, std::string& error)
{
	const JsonValue* temporalValue = nullptr;
	if (!OptionalObjectField(manifestJson, "temporal", temporalValue, manifestPath, error))
		return false;
	if (!temporalValue)
		return true;

	const JsonValue* enabledValue = temporalValue->find("enabled");
	if (!enabledValue || !enabledValue->asBoolean(false))
		return true;

	std::string historySourceName;
	if (!RequireNonEmptyStringField(*temporalValue, "historySource", historySourceName, manifestPath, error))
	{
		error = "Temporal shader is missing required 'historySource' in: " + ManifestPathMessage(manifestPath);
		return false;
	}

	const JsonValue* historyLengthValue = temporalValue->find("historyLength");
	if (!historyLengthValue || !historyLengthValue->isNumber())
	{
		error = "Temporal shader is missing required numeric 'historyLength' in: " + ManifestPathMessage(manifestPath);
		return false;
	}

	TemporalHistorySource historySource = TemporalHistorySource::None;
	if (!ParseTemporalHistorySource(historySourceName, historySource))
	{
		error = "Unsupported temporal historySource '" + historySourceName + "' in: " + ManifestPathMessage(manifestPath);
		return false;
	}

	const double requestedHistoryLength = historyLengthValue->asNumber();
	if (!IsFiniteNumber(requestedHistoryLength) || requestedHistoryLength <= 0.0 || std::floor(requestedHistoryLength) != requestedHistoryLength)
	{
		error = "Temporal shader 'historyLength' must be a positive integer in: " + ManifestPathMessage(manifestPath);
		return false;
	}

	shaderPackage.temporal.enabled = true;
	shaderPackage.temporal.historySource = historySource;
	shaderPackage.temporal.requestedHistoryLength = static_cast<unsigned>(requestedHistoryLength);
	shaderPackage.temporal.effectiveHistoryLength = std::min(shaderPackage.temporal.requestedHistoryLength, maxTemporalHistoryFrames);
	return true;
}

bool ParseFeedbackSettings(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
{
	const JsonValue* feedbackValue = nullptr;
	if (!OptionalObjectField(manifestJson, "feedback", feedbackValue, manifestPath, error))
		return false;
	if (!feedbackValue)
		return true;

	const JsonValue* enabledValue = feedbackValue->find("enabled");
	if (!enabledValue || !enabledValue->asBoolean(false))
		return true;

	shaderPackage.feedback.enabled = true;
	if (!OptionalStringField(*feedbackValue, "writePass", shaderPackage.feedback.writePassId, "", manifestPath, error))
		return false;

	if (shaderPackage.feedback.writePassId.empty())
	{
		if (shaderPackage.passes.empty())
		{
			error = "Feedback-enabled shader has no passes to target in: " + ManifestPathMessage(manifestPath);
			return false;
		}
		shaderPackage.feedback.writePassId = shaderPackage.passes.back().id;
	}

	if (!ValidateShaderIdentifier(shaderPackage.feedback.writePassId, "feedback.writePass", manifestPath, error))
		return false;

	const auto passIt = std::find_if(shaderPackage.passes.begin(), shaderPackage.passes.end(),
		[&shaderPackage](const ShaderPassDefinition& pass) { return pass.id == shaderPackage.feedback.writePassId; });
	if (passIt == shaderPackage.passes.end())
	{
		error = "Feedback writePass '" + shaderPackage.feedback.writePassId + "' does not match any declared pass in: " + ManifestPathMessage(manifestPath);
		return false;
	}

	return true;
}

bool ParseParameterNumberField(const JsonValue& parameterJson, const char* fieldName, std::vector<double>& values, const std::filesystem::path& manifestPath, std::string& error)
{
	if (const JsonValue* fieldValue = parameterJson.find(fieldName))
		return NumberListFromJsonValue(*fieldValue, values, fieldName, manifestPath, error);
	return true;
}

bool ParseParameterDefault(const JsonValue& parameterJson, ShaderParameterDefinition& definition, const std::filesystem::path& manifestPath, std::string& error)
{
	const JsonValue* defaultValue = parameterJson.find("default");
	if (!defaultValue)
		return true;

	if (definition.type == ShaderParameterType::Boolean)
	{
		if (!defaultValue->isBoolean())
		{
			error = "Boolean parameter default must be a boolean for: " + definition.id;
			return false;
		}
		definition.defaultBoolean = defaultValue->asBoolean(false);
		return true;
	}

	if (definition.type == ShaderParameterType::Enum)
	{
		if (!defaultValue->isString())
		{
			error = "Enum parameter default must be a string for: " + definition.id;
			return false;
		}
		definition.defaultEnumValue = defaultValue->asString();
		return true;
	}

	if (definition.type == ShaderParameterType::Text)
	{
		if (!defaultValue->isString())
		{
			error = "Text parameter default must be a string for: " + definition.id;
			return false;
		}
		definition.defaultTextValue = defaultValue->asString();
		return true;
	}

	return NumberListFromJsonValue(*defaultValue, definition.defaultNumbers, "default", manifestPath, error);
}

bool ParseParameterOptions(const JsonValue& parameterJson, ShaderParameterDefinition& definition, const std::filesystem::path& manifestPath, std::string& error)
{
	const JsonValue* optionsValue = nullptr;
	if (!OptionalArrayField(parameterJson, "options", optionsValue, manifestPath, error) || !optionsValue)
	{
		error = "Enum parameter is missing 'options' in: " + ManifestPathMessage(manifestPath);
		return false;
	}

	for (const JsonValue& optionJson : optionsValue->asArray())
	{
		if (!optionJson.isObject())
		{
			error = "Enum parameter option must be an object in: " + ManifestPathMessage(manifestPath);
			return false;
		}

		ShaderParameterOption option;
		if (!RequireStringField(optionJson, "value", option.value, manifestPath, error) ||
			!RequireStringField(optionJson, "label", option.label, manifestPath, error))
		{
			error = "Enum parameter option is missing 'value' or 'label' in: " + ManifestPathMessage(manifestPath);
			return false;
		}
		definition.enumOptions.push_back(option);
	}

	bool defaultFound = definition.defaultEnumValue.empty();
	for (const ShaderParameterOption& option : definition.enumOptions)
	{
		if (option.value == definition.defaultEnumValue)
		{
			defaultFound = true;
			break;
		}
	}

	if (!defaultFound)
	{
		error = "Enum parameter default is not present in its option list for: " + definition.id;
		return false;
	}

	return true;
}

bool ParseParameterDefinition(const JsonValue& parameterJson, ShaderParameterDefinition& definition, const std::filesystem::path& manifestPath, std::string& error)
{
	if (!parameterJson.isObject())
	{
		error = "Shader parameter entry must be an object in: " + ManifestPathMessage(manifestPath);
		return false;
	}

	std::string typeName;
	if (!RequireStringField(parameterJson, "id", definition.id, manifestPath, error) ||
		!RequireStringField(parameterJson, "label", definition.label, manifestPath, error) ||
		!RequireStringField(parameterJson, "type", typeName, manifestPath, error))
	{
		error = "Shader parameter is missing required fields in: " + ManifestPathMessage(manifestPath);
		return false;
	}

	if (!ParseShaderParameterType(typeName, definition.type))
	{
		error = "Unsupported parameter type '" + typeName + "' in: " + ManifestPathMessage(manifestPath);
		return false;
	}
	if (!ValidateShaderIdentifier(definition.id, "parameters[].id", manifestPath, error))
		return false;

	if (!OptionalStringField(parameterJson, "description", definition.description, "", manifestPath, error))
		return false;

	if (!ParseParameterDefault(parameterJson, definition, manifestPath, error) ||
		!ParseParameterNumberField(parameterJson, "min", definition.minNumbers, manifestPath, error) ||
		!ParseParameterNumberField(parameterJson, "max", definition.maxNumbers, manifestPath, error) ||
		!ParseParameterNumberField(parameterJson, "step", definition.stepNumbers, manifestPath, error))
	{
		return false;
	}

	if (definition.type == ShaderParameterType::Text)
	{
		if (const JsonValue* fontValue = parameterJson.find("font"))
		{
			if (!fontValue->isString())
			{
				error = "Text parameter 'font' must be a string for: " + definition.id;
				return false;
			}
			definition.fontId = fontValue->asString();
			if (!definition.fontId.empty() && !ValidateShaderIdentifier(definition.fontId, "parameters[].font", manifestPath, error))
				return false;
		}
		if (const JsonValue* maxLengthValue = parameterJson.find("maxLength"))
		{
			if (!maxLengthValue->isNumber() || maxLengthValue->asNumber() < 1.0 || maxLengthValue->asNumber() > 256.0)
			{
				error = "Text parameter 'maxLength' must be a number from 1 to 256 for: " + definition.id;
				return false;
			}
			definition.maxLength = static_cast<unsigned>(maxLengthValue->asNumber());
		}
	}

	if (definition.type == ShaderParameterType::Enum)
		return ParseParameterOptions(parameterJson, definition, manifestPath, error);

	return true;
}

bool ParseParameterDefinitions(const JsonValue& manifestJson, ShaderPackage& shaderPackage, const std::filesystem::path& manifestPath, std::string& error)
{
	const JsonValue* parametersValue = nullptr;
	if (!OptionalArrayField(manifestJson, "parameters", parametersValue, manifestPath, error))
		return false;
	if (!parametersValue)
		return true;

	for (const JsonValue& parameterJson : parametersValue->asArray())
	{
		ShaderParameterDefinition definition;
		if (!ParseParameterDefinition(parameterJson, definition, manifestPath, error))
			return false;
		shaderPackage.parameters.push_back(definition);
	}

	return true;
}

std::string UniqueUnavailableShaderId(const std::filesystem::path& manifestPath, const std::string& parsedId)
{
	const std::string fallbackId = manifestPath.parent_path().filename().string();
	const std::string baseId = parsedId.empty() ? fallbackId : parsedId;
	return baseId + "@invalid:" + fallbackId;
}

ShaderPackageStatus BuildUnavailableStatus(const std::filesystem::path& manifestPath, const ShaderPackage& partialPackage, const std::string& packageError)
{
	ShaderPackageStatus status;
	status.id = UniqueUnavailableShaderId(manifestPath, partialPackage.id);
	status.displayName = !partialPackage.displayName.empty() ? partialPackage.displayName : manifestPath.parent_path().filename().string();
	status.description = partialPackage.description;
	status.category = !partialPackage.category.empty() ? partialPackage.category : "Unavailable";
	status.available = false;
	status.error = packageError;
	return status;
}

ShaderPackageStatus BuildAvailableStatus(const ShaderPackage& shaderPackage)
{
	ShaderPackageStatus status;
	status.id = shaderPackage.id;
	status.displayName = shaderPackage.displayName;
	status.description = shaderPackage.description;
	status.category = shaderPackage.category;
	status.available = true;
	return status;
}
}

ShaderPackageRegistry::ShaderPackageRegistry(unsigned maxTemporalHistoryFrames)
	: mMaxTemporalHistoryFrames(maxTemporalHistoryFrames)
{
}

bool ShaderPackageRegistry::Scan(
	const std::filesystem::path& shaderRoot,
	std::map<std::string, ShaderPackage>& packagesById,
	std::vector<std::string>& packageOrder,
	std::vector<ShaderPackageStatus>& packageStatuses,
	std::string& error) const
{
	packagesById.clear();
	packageOrder.clear();
	packageStatuses.clear();

	if (!std::filesystem::exists(shaderRoot))
	{
		error = "Shader library directory does not exist: " + shaderRoot.string();
		return false;
	}

	for (const auto& entry : std::filesystem::directory_iterator(shaderRoot))
	{
		if (!entry.is_directory())
			continue;

		std::filesystem::path manifestPath = entry.path() / "shader.json";
		if (!std::filesystem::exists(manifestPath))
			continue;

		ShaderPackage shaderPackage;
		if (!ParseManifest(manifestPath, shaderPackage, error))
		{
			packageStatuses.push_back(BuildUnavailableStatus(manifestPath, shaderPackage, error));
			error.clear();
			continue;
		}

		if (packagesById.find(shaderPackage.id) != packagesById.end())
		{
			packageStatuses.push_back(BuildUnavailableStatus(manifestPath, shaderPackage, "Duplicate shader id found: " + shaderPackage.id));
			continue;
		}

		packageOrder.push_back(shaderPackage.id);
		packageStatuses.push_back(BuildAvailableStatus(shaderPackage));
		packagesById[shaderPackage.id] = shaderPackage;
	}

	std::sort(packageOrder.begin(), packageOrder.end());
	std::sort(packageStatuses.begin(), packageStatuses.end(), [](const ShaderPackageStatus& left, const ShaderPackageStatus& right) {
		return left.displayName < right.displayName;
	});
	return true;
}

bool ShaderPackageRegistry::ParseManifest(const std::filesystem::path& manifestPath, ShaderPackage& shaderPackage, std::string& error) const
{
	const std::string manifestText = ReadTextFile(manifestPath, error);
	if (manifestText.empty())
		return false;

	JsonValue manifestJson;
	if (!ParseJson(manifestText, manifestJson, error))
		return false;
	if (!manifestJson.isObject())
	{
		error = "Shader manifest root must be an object: " + manifestPath.string();
		return false;
	}

	if (!ParseShaderMetadata(manifestJson, shaderPackage, manifestPath, error))
		return false;

	if (!ParsePassDefinitions(manifestJson, shaderPackage, manifestPath, error))
		return false;

	shaderPackage.shaderWriteTime = shaderPackage.passes.front().sourceWriteTime;
	for (const ShaderPassDefinition& pass : shaderPackage.passes)
	{
		if (pass.sourceWriteTime > shaderPackage.shaderWriteTime)
			shaderPackage.shaderWriteTime = pass.sourceWriteTime;
	}
	shaderPackage.manifestWriteTime = std::filesystem::last_write_time(shaderPackage.manifestPath);

	return ParseTextureAssets(manifestJson, shaderPackage, manifestPath, error) &&
		ParseFontAssets(manifestJson, shaderPackage, manifestPath, error) &&
		ParseTemporalSettings(manifestJson, shaderPackage, mMaxTemporalHistoryFrames, manifestPath, error) &&
		ParseFeedbackSettings(manifestJson, shaderPackage, manifestPath, error) &&
		ParseParameterDefinitions(manifestJson, shaderPackage, manifestPath, error);
}
