const std = @import("std");
const _registry = @import("registry.zig");
const _model = @import("model.zig");
const _dispatchers = @import("dispatchers.zig");


/// Type of dispatchers map.
pub const DispatchersMap = std.StaticStringMap(_dispatchers.Dispatcher);

/// Type of models dispatchers map.
pub const ModelsDispatchersMap = std.StaticStringMap(DispatchersMap);

/// Build dispatchers maps of all models from the dispatchers definition structure.
fn BuildModelsDispatchersMaps(comptime dispatchersDefinition: type) ModelsDispatchersMap {
	return ModelsDispatchersMap.initComptime(comptime dispatchersKv: {
		// Get all models dispatchers definition structures.
		const modelsDispatchersDecls = std.meta.declarations(dispatchersDefinition);

		// Initialize a new key-values array for all models.
		var dispatchersKv: [modelsDispatchersDecls.len]struct{[]const u8, DispatchersMap} = undefined;

		// Build all dispatchers maps and add them in the key-values array.
		for (&dispatchersKv, modelsDispatchersDecls) |*dispatcherKv, modelDispatchersDecl| {
			dispatcherKv.* = .{ modelDispatchersDecl.name, BuildDispatchersMap(modelDispatchersDecl.name, @field(dispatchersDefinition, modelDispatchersDecl.name)) };
		}

		// Return the new dispatchers maps key-values.
		break :dispatchersKv dispatchersKv;
	});
}

/// Build a dispatchers map for a model from its dispatchers definition structure.
fn BuildDispatchersMap(comptime modelName: []const u8, comptime dispatchersBuilders: type) DispatchersMap {
	// Return the new dispatchers map.
	return DispatchersMap.initComptime(comptime dispatchersKv: {
		// Get all dispatchers declarations.
		const dispatchersDecls = std.meta.declarations(dispatchersBuilders);

		// Initialize a new key-values array for all dispatchers.
		var dispatchersKv: [dispatchersDecls.len]struct{[]const u8, _dispatchers.Dispatcher} = undefined;

		// Build all dispatchers and add them in the key-values array.
		for (dispatchersKv[0..dispatchersDecls.len], dispatchersDecls) |*dispatcherKv, dispatcherDecl| {
			dispatcherKv.* = .{ dispatcherDecl.name, @field(dispatchersBuilders, dispatcherDecl.name).build(modelName, dispatcherDecl.name) };
		}

		// Return the new dispatchers key-values.
		break :dispatchersKv dispatchersKv;
	});
}


/// MQL API structure.
pub fn Api(
	comptime registry: type,
	comptime dispatchersDefinition: type,
) type {
	// Build models dispatchers map.
	const dispatchersMap = BuildModelsDispatchersMaps(dispatchersDefinition);

	// Compute dispatchers array size.
	const dispatchersArraySize = comptime dispatchersArraySize: {
		var size = 0;

		for (dispatchersMap.values()) |dispatchers| {
			size += dispatchers.values().len;
		}

		break :dispatchersArraySize size;
	};
	// Build dispatchers array.
	const dispatchersArray = comptime dispatchersArray: {
		var dispatchersArray: [dispatchersArraySize]_dispatchers.Dispatcher = undefined;
		var dispatchersArrayIndex = 0;

		for (dispatchersMap.values()) |dispatchers| {
			for (dispatchers.values()) |dispatcher| {
				dispatchersArray[dispatchersArrayIndex] = dispatcher;
				dispatchersArrayIndex += 1;
			}
		}

		break :dispatchersArray dispatchersArray;
	};

	return struct {
		pub const Registry = registry;
		pub const dispatchers = dispatchersMap;

		/// Get model name from its provided instance.
		pub fn getModelName(comptime model: _model.Model) []const u8 {
			return Registry.getModelName(model);
		}

		/// Get a list of all declared dispatchers.
		pub fn getDispatchers() []const _dispatchers.Dispatcher {
			return dispatchersArray[0..dispatchersArraySize];
		}
	};
}