local meld = require("meld")
local designs = require("designs")

quality_glassware = quality_glassware or {}

---Get properties suitable for putting into an item prototype to display a given flask variant.
---
---**Example**
---```lua
---meld(
---    data.raw["tool"]["automation-science-pack"],
---    quality_glassware.item_graphics_for(variant, meld),
--- )
----- get icon layer for composing
---local layer = quality_glassware.item_graphics_for(variant)
---```
---@param variant Variant The variant of flask to provide the graphics for.
---@param meld any? The meld library obtain from `require("meld")`, if passed the output contains a meld directives to remove the icons property.
---@return table
function quality_glassware.item_graphics_for(variant, meld)
	local graphics = {
		icon_size = 64,
		icon = "__quality_glassware__/graphics/icons/" .. variant.model .. "_" .. variant.name:gsub("liquid", "clear") .. ".png",
	}
	if meld then
		graphics.icons = meld.delete()
	end
	return graphics
end

---Get properties suitable for putting into an technology prototype to display a given flask variant.
---
---**Example**
---```lua
----- update a prototype
---meld(
---    data.raw["technology"]["automation-science-pack"],
---    quality_glassware.technology_graphics_for(variant, meld),
--- )
----- get icon layer for composing
---local layer = quality_glassware.technology_graphics_for(variant)
---```
---@param variant Variant The variant of flask to provide the graphics for.
---@param meld any? The meld library obtain from `require("meld")`, if passed the output contains a meld directives to remove the icons property.
---@return { icon_size: number, icon: string, icons: any? }
function quality_glassware.technology_graphics_for(variant, meld)
	local graphics = {
		icon_size = 256,
		icon = "__quality_glassware__/graphics/technology/" .. variant.model .. "_" .. variant.name:gsub("liquid", "clear") .. ".png",
	}
	if meld then
		graphics.icons = meld.delete()
	end
	return graphics
end

---@alias Request { model: ModelId?, variant: VariantName?, contents: QGContents? } A request for a given glassware.

---(Advanced usage) Try a request to see what variant it results in
---
---Allows you to check what a request would result in before passing it to
---[request_flask](lua://quality_glassware.request_flask).
---
---Note that this does not account for id already being assigned or overriden, to check that
---see [get_flask](lua://quality_glassware.get_flask). If the return value is nil it means the next
---request in the chain would get checked.
---
---@param request Request the request to try.
---@return Variant? variant the resolved variant, or nil if the request could not be satisfied.
function quality_glassware.try_request(request)
	if request.model and request.variant then
		local variant =
			designs.models[request.model]
			and designs.models[request.model].variants[request.variant]
		if not variant or variant.assigned_to then
			return nil
		end
		return variant
	end

	if request.model then
		local model = designs.models[request.model]
		if not model then
			return nil
		end
		for _, variant in pairs(model.variants) do
			if variant.contents == (request.contents or "liquid") and not variant.assigned_to then
				return variant
			end
		end
		return nil
	end

	if request.variant then
		for _, model in pairs(designs.models) do
			local variant = model.variants[request.variant]
			if variant and not variant.assigned_to then
				return variant
			end
		end
		return nil
	end

	for _, model in pairs(designs.models) do
		for _, variant in pairs(model.variants) do
			if variant.contents == (request.contents or "liquid") and not variant.assigned_to then
				return variant
			end
		end
	end
	return nil
end

local assigned_variants = {}
---Get design associated with a given id
---@param id string String previously passed to [request_flask](lua://quality_glassware.request_flask)
---@return Variant? variant The design associated with the given id, if any.
function quality_glassware.get_flask(id)
	return assigned_variants[id]
end

local overrides = settings.startup.quality_glassware_overrides.value
if overrides ~= "" then
	for override in overrides:gmatch("[^,]+") do
		id, model, variant = override:match("^ *([^=/ ,]+)=([^/ ,]+)/([^ ,]+) *$")
		if not id or not model or not variant then
			log(string.format("Failed to parse override '%s', expected format is id=model/variant", override))
		elseif not designs.models[model] then
			log(string.format("Failed apply override %s: Model '%s' does not exist", override, model))
		elseif not designs.models[model].variants[variant] then
			log(string.format("Failed apply override %s: Model '%s' does not have variant '%s'", override, model, variant))
		else
			log(string.format("Quality Glassware overriding %s=%s/%s", id, model, variant))
			assigned_variants[id] = designs.models[model].variants[variant]
			assigned_variants[id].assigned_to = id
		end
	end
end

---Request a unique flask design for use in a mod
---
---Interface to reserve a unique flask design for a mod to use based on an array of requests that specificy what kind of design is acceptable.
---Each request is table that can contain the following three properties all of which are optional
---- model [ModelId](lua://ModelId): kind of flask to get.
---- variant [VariantName](lua://VariantName): variant to get. For example clear red liquid is "liquid_red", an empty flask is "empty".
---- contents [QGContents](lua://QGContents): Contents the bottle should have. Defaults to "liquid" and is ignored if variant is present.
---
---The requests are tried in order until one of them finds design that haven't already been taken.
---If no requests succeed a design is picked at random as a fallback so that the game will still load.
---The design picked is then associated with the id passed so subsequent calls to request_flask with the same id will always give the same result.
---Note that if the flask design have been overridden by the quality_glassware_overrides startup config then that design will be returned instead.
---
---**Example**
---```lua
---local variant = quality_glassware.request_flask(
---    "my-science-pack",
---    {
---        { model = "cone_normal", variant = "liquid_red" }, -- Try a conical red flask
---        { model = "sphere_normal", variant = "liquid_red" }, -- If that's taken try a sphereical red flask.
---        { variant = "liquid_red" }, -- If that's also taken, try any red flask.
---    }
---)
----- Pass variant to icon_graphics_for or technology_graphics_for to get icon paths that can be rendered.
---```
---
---@param id string The prototype id to request a flask for. This is used for overrides and avoiding duplicates.
---@param requests table<number, Request> Array of requests to evaluate. First request to succeed is returned.
---@return Variant variant The flask variant the request resulted in.
---
function quality_glassware.request_flask(id, requests)
	if assigned_variants[id] then
		return assigned_variants[id]
	end

	local variant
	if requests then
		for _, request in pairs(requests) do
			variant = quality_glassware.try_request(request)
			if variant then
				break
			end
		end
	end

	-- If all requests fail give something so the game loads
	local fallback = ""
	if not variant then
		variant = quality_glassware.try_request({})
		fallback = " (fallback)"
		if not variant then
			-- There's over 200 variants, how did you run out?
			error("quality_glassware has no available variants left!")
		end
	end

	log(string.format("Quality Glassware assigned %s=%s/%s%s", id, variant.model, variant.name, fallback))
	variant.assigned_to = id
	assigned_variants[id] = variant
	return variant
end

local function create_debug_prototypes(variant, index)
	local model = variant.model
	local name = "qw-debug-" .. model .. "-" .. variant.name
	data:extend{
		meld(
			{
				type = "item",
				name = name,
				localised_name = {
					"quality_glassware.glassware_" .. variant.contents,
					{"quality_glassware." .. (variant.color or "invalid")},
					{"quality_glassware." .. variant.model},
				},
				order = string.format("a[%s]%02i", model, index),
				stack_size = 10,
			},
			quality_glassware.item_graphics_for(variant)
		),
		meld(
			{
				type = "technology",
				name = name,
				localised_name = {
					"quality_glassware.glassware_" .. variant.contents,
					{"quality_glassware." .. (variant.color or "invalid")},
					{"quality_glassware." .. variant.model},
				},
				order = string.format("a[%s]%02i", model, index),
				unit = {
					time = 30,
					count = 1,
					ingredients = {{name, 1}},
				}
			},
			quality_glassware.technology_graphics_for(variant)
		)
	}
	local lab_inputs = data.raw.lab.lab.inputs
	lab_inputs[#lab_inputs + 1] = name
end

if settings.startup.quality_glassware_create_debug_prototypes.value then
	for _, model in pairs(designs.models) do
		local index = 0
		for _, variant in pairs(model.variants) do
			create_debug_prototypes(variant, index)
			index = index + 1
		end
	end
end

-- For advanced usage
quality_glassware.models = designs.models
quality_glassware.colors = designs.colors
quality_glassware.contents = designs.contents
