package dev.latvian.mods.kubejs.registry;

import dev.latvian.mods.kubejs.DevProperties;
import dev.latvian.mods.kubejs.error.KubeRuntimeException;
import dev.latvian.mods.kubejs.event.EventResult;
import dev.latvian.mods.kubejs.event.KubeStartupEvent;
import dev.latvian.mods.kubejs.script.ConsoleJS;
import dev.latvian.mods.kubejs.script.SourceLine;
import dev.latvian.mods.kubejs.util.KubeIdentifier;
import dev.latvian.mods.rhino.Context;
import net.minecraft.core.Registry;
import net.minecraft.resources.ResourceKey;

import java.util.LinkedList;
import java.util.List;
import java.util.function.Supplier;

public class RegistryKubeEvent<T> implements KubeStartupEvent, AdditionalObjectRegistry {
	private final ResourceKey<Registry<T>> registryKey;
	private final BuilderTypeRegistryHandler.Info<T> builderInfo;
	public final List<BuilderBase<? extends T>> created;

	public RegistryKubeEvent(ResourceKey<Registry<T>> registryKey) {
		this.registryKey = registryKey;
		this.builderInfo = BuilderTypeRegistryHandler.info(registryKey);
		this.created = new LinkedList<>();
	}

	public BuilderBase<? extends T> create(Context cx, KubeIdentifier id, KubeIdentifier type) {
		var sourceLine = SourceLine.of(cx);
		var t = builderInfo.namedType(type.wrapped());

		if (t == null) {
			throw new KubeRuntimeException("Unknown type '" + type + "' for object '" + id + "'!").source(sourceLine);
		}

		var b = t.factory().createBuilder(id.wrapped());

		if (b == null) {
			throw new KubeRuntimeException("Unknown type '" + type + "' for object '" + id + "'!").source(sourceLine);
		} else if (builderInfo.directCodec() != null) {
			throw new KubeRuntimeException("Type '" + type + "' for object '" + id + "' is a datapack registry type!").source(sourceLine);
		} else {
			b.sourceLine = sourceLine;
			b.registryKey = registryKey;
			addBuilder(b);
			created.add(b);
		}

		return b;
	}

	public BuilderBase<? extends T> create(Context cx, KubeIdentifier id) {
		var sourceLine = SourceLine.of(cx);
		var t = builderInfo.defaultType();

		if (t == null) {
			throw new KubeRuntimeException("Registry '" + registryKey.identifier() + "' doesn't have a default type registered!").source(sourceLine);
		}

		var b = t.factory().createBuilder(id.wrapped());

		if (b == null) {
			throw new KubeRuntimeException("Unknown type '" + t.type() + "' for object '" + id + "'!").source(sourceLine);
		} else {
			b.sourceLine = sourceLine;
			b.registryKey = registryKey;
			addBuilder(b);
			created.add(b);
		}

		return b;
	}

	public CustomBuilderObject createCustom(Context cx, KubeIdentifier id, Supplier<Object> object) {
		var sourceLine = SourceLine.of(cx);

		if (object == null) {
			throw new KubeRuntimeException("Tried to register a null object with id: " + id).source(sourceLine);
		}

		var b = new CustomBuilderObject(id.wrapped(), object);
		b.sourceLine = sourceLine;
		b.registryKey = registryKey;
		addBuilder(b);
		created.add(b);
		return b;
	}

	@Override
	public void afterPosted(EventResult result) {
		for (var c : created) {
			c.createAdditionalObjects(this);
		}
	}

	@Override
	public <R> void add(ResourceKey<Registry<R>> registry, BuilderBase<? extends R> builder) {
		builder.registryKey = (ResourceKey) registry;
		addBuilder(builder);
	}

	private <R> void addBuilder(BuilderBase<? extends R> builder) {
		if (builder == null) {
			throw new IllegalArgumentException("Can't add null builder in registry '" + builder.registryKey.identifier() + "'!");
		}

		if (DevProperties.get().logRegistryEventObjects) {
			ConsoleJS.STARTUP.info("~ " + builder.registryKey.identifier() + " | " + builder.id);
		}

		var objStorage = RegistryObjectStorage.of(builder.registryKey);

		if (objStorage.objects.containsKey(builder.id)) {
			throw new IllegalArgumentException("Duplicate key '" + builder.id + "' in registry '" + builder.registryKey.identifier() + "'!");
		}

		objStorage.objects.put(builder.id, (BuilderBase) builder);
		RegistryObjectStorage.ALL_BUILDERS.add(builder);

		// registry.deferredRegister.register()
	}
}