local StudioService = game:GetService("StudioService")
local AssetService = game:GetService("AssetService")

type CachedImageInfo = {
	pixels: buffer,
	size: Vector2,
}

local Rojo = script:FindFirstAncestor("Rojo")
local Plugin = Rojo.Plugin
local Packages = Rojo.Packages

local Roact = require(Packages.Roact)

local e = Roact.createElement

local EditableImage = require(Plugin.App.Components.EditableImage)

local imageCache: { [string]: CachedImageInfo } = {}

local function cloneBuffer(b: buffer): buffer
	local newBuffer = buffer.create(buffer.len(b))
	buffer.copy(newBuffer, 0, b)
	return newBuffer
end

local function getImageSizeAndPixels(image: string): (Vector2, buffer)
	local cachedImage = imageCache[image]

	if not cachedImage then
		local editableImage = AssetService:CreateEditableImageAsync(Content.fromUri(image))
		local size = editableImage.Size
		local pixels = editableImage:ReadPixelsBuffer(Vector2.zero, size)
		imageCache[image] = {
			pixels = pixels,
			size = size,
		}

		return size, cloneBuffer(pixels)
	end

	return cachedImage.size, cloneBuffer(cachedImage.pixels)
end

local function getRecoloredClassIcon(className, color)
	local iconProps = StudioService:GetClassIcon(className)

	if iconProps and color then
		--stylua: ignore
		local success, editableImageSize, editableImagePixels = pcall(function(_iconProps: { [any]: any }, _color: Color3): (Vector2, buffer)
			local size, pixels = getImageSizeAndPixels(_iconProps.Image)
			local pixelsLen = buffer.len(pixels)

			local minVal, maxVal = math.huge, -math.huge

			for i = 0, pixelsLen, 4 do
				if buffer.readu8(pixels, i + 3) == 0 then
					continue
				end
				local pixelVal = math.max(
					buffer.readu8(pixels, i),
					buffer.readu8(pixels, i + 1),
					buffer.readu8(pixels, i + 2)
				)

				minVal = math.min(minVal, pixelVal)
				maxVal = math.max(maxVal, pixelVal)
			end

			local hue, sat, val = _color:ToHSV()

			for i = 0, pixelsLen, 4 do
				if buffer.readu8(pixels, i + 3) == 0 then
					continue
				end
				local gIndex = i + 1
				local bIndex = i + 2

				local pixelVal = math.max(
					buffer.readu8(pixels, i),
					buffer.readu8(pixels, gIndex),
					buffer.readu8(pixels, bIndex)
				)
				local newVal = val
				if minVal < maxVal then
					-- Remap minVal - maxVal to val*0.9 - val
					newVal = val * (0.9 + 0.1 * (pixelVal - minVal) / (maxVal - minVal))
				end

				local newPixelColor = Color3.fromHSV(hue, sat, newVal)
				buffer.writeu8(pixels, i, newPixelColor.R)
				buffer.writeu8(pixels, gIndex, newPixelColor.G)
				buffer.writeu8(pixels, bIndex, newPixelColor.B)
			end
			return size, pixels
		end, iconProps, color)
		if success then
			iconProps.EditableImagePixels = editableImagePixels
			iconProps.EditableImageSize = editableImageSize
		end
	end

	return iconProps
end

local ClassIcon = Roact.PureComponent:extend("ClassIcon")

function ClassIcon:init()
	self.state = {
		iconProps = nil,
	}
end

function ClassIcon:updateIcon()
	local props = self.props
	local iconProps = getRecoloredClassIcon(props.className, props.color)
	self:setState({
		iconProps = iconProps,
	})
end

function ClassIcon:didMount()
	self:updateIcon()
end

function ClassIcon:didUpdate(lastProps)
	if lastProps.className ~= self.props.className or lastProps.color ~= self.props.color then
		self:updateIcon()
	end
end

function ClassIcon:render()
	local iconProps = self.state.iconProps
	if not iconProps then
		return nil
	end

	return e(
		"ImageLabel",
		{
			Size = self.props.size,
			Position = self.props.position,
			LayoutOrder = self.props.layoutOrder,
			AnchorPoint = self.props.anchorPoint,
			ImageTransparency = self.props.transparency,
			Image = iconProps.Image,
			ImageRectOffset = iconProps.ImageRectOffset,
			ImageRectSize = iconProps.ImageRectSize,
			BackgroundTransparency = 1,
		},
		if iconProps.EditableImagePixels
			then e(EditableImage, {
				size = iconProps.EditableImageSize,
				pixels = iconProps.EditableImagePixels,
			})
			else nil
	)
end

return ClassIcon
