local function serialize_stack(stack)
    return {stack:get_name() or "", stack:get_definition().type == "tool" and stack:get_wear() or stack:get_count()}
end
item_defs = {}
usages = {}
crafts = {}
groups = {}
local function get_craft_recipes(def_name)
    local item_crafts = minetest.get_all_craft_recipes(def_name)
    if not item_crafts then
        return
    end
    for index, craft in ipairs(item_crafts) do
        local stack = ItemStack(craft.output)
        local craft_items = {}
        local width = craft.width > 0 and craft.width or 1
        for i = #craft.items, width, -width do
            local keep_line
            for j = i, math.max(1, i-width), -1 do
                local item = craft.items[j]
                if item and item ~= "" then
                    keep_line = true
                    break
                end
            end
            if not keep_line then
                for j = i, math.max(1, i-width), -1 do
                    table.remove(craft.items, j)
                end
            end
        end
        local maxidx = 1
        for index, item in pairs(craft.items) do
            craft_items[index] = serialize_stack(ItemStack(item))
            maxidx = math.max(maxidx, index)
        end
        for i = 1, maxidx do
            if not craft_items[i] then
                craft_items[i] = {}
            end
        end
        local craft_index = #crafts
        item_crafts[index] = craft_index
        table.insert(crafts, {
            method = craft.method,
            shapeless = craft.width == 0,
            width = craft.width ~= 0 and math.min(#craft_items, craft.width),
            items = craft_items,
            output = serialize_stack(stack)
        })
        for _, item in ipairs(craft.items or {}) do
            local itemname = ItemStack(item):get_name()
            if not item_defs[itemname] then
                usages[itemname] = {craft_index}
            else
                local tab = item_defs[itemname].usages
                if not tab then
                    tab = {}
                    item_defs[itemname].usages = tab
                end
                if not modlib.table.find(tab, craft_index) then
                    table.insert(tab, craft_index)
                end
            end
        end
    end
    return item_crafts
end
local handle = io.open(minetest.get_modpath("online_craftguide").."/docs/index.html", "w")
function minetest_to_html(text)
    local previous_color
    text = text:gsub("<", "&lt;"):gsub("'", "&apos;"):gsub('"', "&quot;"):gsub("\x1b%((%a)@(.-)%)", function(type, args)
        if type == "c" and previous_color ~= args then
            local retval = (previous_color and "</span>" or "").."<span style='color: "..args.." !important;'>"
            previous_color = args
            return retval
        end
        return ""
    end):gsub("\n", "<br>"):gsub("\x1bE", "")
    return text .. (previous_color and "</span>" or "")
end
function minetest_to_searchable(text)
    return text:gsub("\x1b%((%a)@(.-)%)", ""):gsub("\x1bE", ""):lower()
end
function add_item(name, def)
    if (def.groups and def.groups.not_in_creative_inventory) or def.description == "" then
        return
    end
    local def_name = def.name
    local item = item_defs[def_name]
    if not item then
        local title, description = unpack(modlib.text.split(def.description, "\n", 2))
        item = {
            crafts = get_craft_recipes(def_name),
            title = minetest_to_html(title),
            description = description and minetest_to_html(description) or nil,
            searchable_description = minetest_to_searchable(def.description),
            type = def.type,
            groups = def.groups,
            usages = usages[def.name]
        }
        if def.groups then
            for group, rating in pairs(def.groups) do
                groups[group] = groups[group] or {}
                groups[group][def_name] = rating
            end
        end
        item_defs[def_name] = item
    end
    if name ~= def_name then
        item.aliases = item.aliases or {}
        table.insert(item.aliases, name)
    end
end
function preprocess_html(text)
    local regex = [[(<svg%s+class=['"]bi%sbi%-)(.-)(['"].->.-</svg>)]]
    text = text:gsub(regex, function(_,match)
        local svg = modlib.file.read(minetest.get_modpath("online_craftguide").."/node_modules/bootstrap-icons/icons/"..modlib.text.split(match, "%s", 2, true)[1]..".svg")
        return svg:gsub(regex, function(before, _, after) return before .. match .. after end):gsub("\n", "")
    end)
    local replaced
    text = text:gsub([[<script%s*src=['"]online_craftguide['"]>.-</script>]], function()
        if replaced then
            error("multiple script tags")
        end
        replaced = true
        return "<script>items = "..minetest.write_json(item_defs).."; crafts = "..minetest.write_json(crafts).."; groups = "..minetest.write_json(groups).."</script>"
    end)
    return text
end
minetest.register_on_mods_loaded(function()
    for name, def in pairs(minetest.registered_items) do
        add_item(name, def)
    end
    for _, item in pairs(item_defs) do
        if item.aliases then
            table.sort(item.aliases)
        end
    end
    handle:write(preprocess_html(modlib.file.read(modlib.mod.get_resource("online_craftguide", "index.html"))))
    handle:close()
end)