local _, ns = ...
local SlickUI = ns.SlickUI
local Theme = SlickUI.Theme

local AFKMode = {
	LastScene = nil,
	ManuallyHidden = false,
}
SlickUI.AFKMode = AFKMode

-- New scenes can be added here without changing the display system. Once the
-- list contains more than one entry, AFK Mode selects randomly and avoids
-- immediately repeating the previous scene.
local SCENES = {
	{
		key = "campfire-sleep",
		name = "Campfire Dreams",
		subtitle = "Resting beneath the stars",
		texture = "Interface\\AddOns\\SlickUI\\Media\\AFK\\CampfireSleep.png",
		animation = 100, -- Sleep
		facing = 2.50,
		camera = 1.48,
		playerXPct = 0.350,
		playerYPct = 0.252,
		playerWidthPct = 0.420,
		playerHeightPct = 0.630,
		outfit = { 4344, 10045, 4312, 2580 }, -- Cozy linen and wool
	},
	{
		key = "twilight-fishing",
		name = "Gone Fishing",
		subtitle = "Waiting for the next big catch",
		texture = "Interface\\AddOns\\SlickUI\\Media\\AFK\\TwilightFishing.png",
		animation = 134, -- FishingLoop
		facing = 2.58,
		camera = 1.55,
		playerXPct = 0.105,
		playerYPct = 0.094,
		texCoord = { 0, 1, 0.078125, 0.921875 },
		outfit = { 33820, 6263, 4312, 6256 }, -- Fishing hat, overalls, boots, pole
	},
	{
		key = "stargazing",
		name = "Stargazing",
		subtitle = "Dreaming beneath an endless sky",
		texture = "Interface\\AddOns\\SlickUI\\Media\\AFK\\Stargazing.png",
		animation = 97, -- SitGround
		facing = 2.55,
		camera = 1.48,
		playerXPct = 0.100,
		playerYPct = 0.073,
		texCoord = { 0, 1, 0.078125, 0.921875 },
		outfit = { 2577, 10047, 4312, 7053 }, -- Blue linen, kilt, boots, cloak
	},
	{
		key = "desert-caravan",
		name = "Desert Caravan",
		subtitle = "Following the sun beyond the dunes",
		texture = "Interface\\AddOns\\SlickUI\\Media\\AFK\\DesertCaravan.png",
		animation = 91, -- Mount
		facing = 2.55,
		camera = 1.78,
		playerXPct = 0.144,
		playerYPct = 0.219,
		mountXPct = 0.065,
		mountYPct = -0.042,
		mountCamera = 1.12,
		texCoord = { 0, 1, 0.078125, 0.921875 },
		outfit = { 3829, 3834, 4312, 6795 }, -- Haliscan desert-traveler set
		mountDisplays = { 35136, 35134, 35135 }, -- Brown, tan, and grey riding camels
	},
}

local function IsReadable(value)
	if value == nil then return false end
	if canaccessvalue and not canaccessvalue(value) then return false end
	if issecretvalue and issecretvalue(value) then return false end
	return true
end

local function AddSpecialFrame(name)
	for _, existing in ipairs(UISpecialFrames) do
		if existing == name then return end
	end
	table.insert(UISpecialFrames, name)
end

function AFKMode:IsEnabled()
	return SlickUI.DB and SlickUI.DB.funStuff and SlickUI.DB.funStuff.afkMode == true
end

function AFKMode:IsPlayerAFK()
	local afk = UnitIsAFK("player")
	return IsReadable(afk) and afk == true
end

function AFKMode:ChooseScene()
	if #SCENES == 1 then return SCENES[1] end
	local scene
	repeat scene = SCENES[math.random(1, #SCENES)] until scene.key ~= self.LastScene
	return scene
end

function AFKMode:GetCharacterLabel()
	local name, realm = UnitFullName("player")
	if not IsReadable(name) or type(name) ~= "string" or name == "" then name = "Adventurer" end
	if not IsReadable(realm) or type(realm) ~= "string" or realm == "" then
		realm = GetNormalizedRealmName and GetNormalizedRealmName() or GetRealmName()
	end
	if not IsReadable(realm) or type(realm) ~= "string" or realm == "" then return name end
	return name .. "  •  " .. realm
end

function AFKMode:ApplyMount(scene)
	if not self.MountModel then return end
	self.MountModel:Hide()
	if not scene.mountDisplays then return end
	local mountDisplayID = scene.mountDisplays[math.random(1, #scene.mountDisplays)]
	if type(mountDisplayID) ~= "number" then return end
	self.MountModel:ClearModel()
	self.MountModel:SetDisplayInfo(mountDisplayID)
	if self.MountModel.SetCamDistanceScale then self.MountModel:SetCamDistanceScale(scene.mountCamera or 0.92) end
	if self.MountModel.SetFacing then self.MountModel:SetFacing(scene.facing or 0) end
	if self.MountModel.RefreshCamera then self.MountModel:RefreshCamera() end
	self.MountModel:SetAnimation(0)
	self.MountModel:Show()
end

function AFKMode:ApplyOutfit(scene)
	if self.Model.Undress then self.Model:Undress() end
	if not self.Model.TryOn then return end
	for _, itemID in ipairs(scene.outfit or {}) do
		pcall(self.Model.TryOn, self.Model, "item:" .. itemID)
	end
end

function AFKMode:LayoutScene(scene)
	if not scene or not self.Frame then return end
	local width, height = self.Frame:GetSize()
	if type(width) ~= "number" or width <= 0 or type(height) ~= "number" or height <= 0 then return end

	self.Model:ClearAllPoints()
	self.Model:SetPoint("BOTTOMLEFT", self.Frame, "BOTTOMLEFT", width * (scene.playerXPct or 0.06), height * (scene.playerYPct or 0))
	self.Model:SetSize(width * (scene.playerWidthPct or 0.48), height * (scene.playerHeightPct or 0.72))

	self.MountModel:ClearAllPoints()
	self.MountModel:SetPoint("BOTTOMLEFT", self.Frame, "BOTTOMLEFT", width * (scene.mountXPct or 0.06), height * (scene.mountYPct or -0.04))
	self.MountModel:SetSize(width * 0.52, height * 0.78)
end

function AFKMode:FinishModel(scene, generation)
	if generation ~= self.ModelGeneration or scene ~= self.CurrentScene or not self.Frame:IsShown() then return end
	self:ApplyMount(scene)
	self:ApplyOutfit(scene)
	if self.Model.SetCamDistanceScale then self.Model:SetCamDistanceScale(scene.camera or 1) end
	if self.Model.SetFacing then self.Model:SetFacing(scene.facing or 0) end
	if self.Model.RefreshCamera then self.Model:RefreshCamera() end
	self.Model:SetAnimation(scene.animation or 100)
end

function AFKMode:ApplyScene(scene)
	if not scene or not self.Frame then return end
	self.LastScene = scene.key
	self.CurrentScene = scene
	self.Background:SetTexture(scene.texture)
	self.Background:SetTexCoord(unpack(scene.texCoord or { 0, 1, 0, 1 }))
	self.SceneTitle:SetText(scene.name)
	self.SceneSubtitle:SetText(scene.subtitle)
	self.CharacterLabel:SetText(self:GetCharacterLabel())
	self:LayoutScene(scene)
	self.Model:ClearModel()
	self.Model:SetUnit("player")
	if self.Model.SetCamDistanceScale then self.Model:SetCamDistanceScale(scene.camera or 1) end
	if self.Model.SetFacing then self.Model:SetFacing(scene.facing or 0) end
	self.ModelGeneration = (self.ModelGeneration or 0) + 1
	local generation = self.ModelGeneration
	C_Timer.After(0.10, function() AFKMode:FinishModel(scene, generation) end)
end

function AFKMode:Show()
	if not self:IsEnabled() or not self:IsPlayerAFK() or self.ManuallyHidden then return end
	self.Frame:Show()
	self:ApplyScene(self:ChooseScene())
end

function AFKMode:Hide(manual)
	if manual then self.ManuallyHidden = true end
	if self.Frame then self.Frame:Hide() end
end

function AFKMode:Refresh()
	if self:IsEnabled() and self:IsPlayerAFK() then
		self:Show()
	else
		self.ManuallyHidden = false
		self:Hide(false)
	end
end

function AFKMode:SetEnabled(enabled)
	SlickUI.DB.funStuff = SlickUI.DB.funStuff or {}
	SlickUI.DB.funStuff.afkMode = enabled == true
	self:Refresh()
end

function AFKMode:CreateFrame()
	local frame = CreateFrame("Frame", "SlickUIAFKModeFrame", UIParent)
	frame:SetAllPoints(UIParent)
	frame:SetFrameStrata("FULLSCREEN_DIALOG")
	frame:SetFrameLevel(500)
	frame:EnableMouse(true)
	frame:Hide()

	local background = frame:CreateTexture(nil, "BACKGROUND")
	background:SetAllPoints(frame)
	background:SetTexCoord(0, 1, 0, 1)
	self.Background = background

	local shade = frame:CreateTexture(nil, "BORDER")
	shade:SetAllPoints(frame)
	shade:SetColorTexture(0.015, 0.01, 0.04, 0.20)

	local model = CreateFrame("DressUpModel", nil, frame)
	model:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 140, -30)
	model:SetSize(UIParent:GetWidth() * 0.48, UIParent:GetHeight() * 0.72)
	model:SetFrameStrata("FULLSCREEN_DIALOG")
	model:SetFrameLevel(frame:GetFrameLevel() + 2)
	self.Model = model

	local mountModel = CreateFrame("PlayerModel", nil, frame)
	mountModel:SetPoint("BOTTOMLEFT", frame, "BOTTOMLEFT", 140, -90)
	mountModel:SetSize(UIParent:GetWidth() * 0.52, UIParent:GetHeight() * 0.78)
	mountModel:SetFrameStrata("FULLSCREEN_DIALOG")
	mountModel:SetFrameLevel(frame:GetFrameLevel() + 1)
	mountModel:Hide()
	self.MountModel = mountModel

	local title = Theme:CreateText(frame, 28)
	title:SetPoint("TOP", frame, "TOP", 0, -58)
	title:SetTextColor(1.00, 0.74, 0.38, 1)
	self.SceneTitle = title

	local subtitle = Theme:CreateText(frame, 13)
	subtitle:SetPoint("TOP", title, "BOTTOM", 0, -10)
	subtitle:SetTextColor(0.92, 0.78, 0.88, 1)
	self.SceneSubtitle = subtitle

	local character = Theme:CreateText(frame, 16)
	character:SetPoint("TOP", subtitle, "BOTTOM", 0, -12)
	character:SetTextColor(1.00, 0.52, 0.78, 1)
	self.CharacterLabel = character

	local afk = Theme:CreateText(frame, 12)
	afk:SetPoint("BOTTOM", frame, "BOTTOM", 0, 38)
	afk:SetText("AFK MODE  •  Move, press a key, or click to return")
	afk:SetTextColor(1, 1, 1, 0.82)

	frame:SetScript("OnMouseDown", function() AFKMode:Hide(true) end)
	frame:SetScript("OnSizeChanged", function()
		if AFKMode.CurrentScene then AFKMode:LayoutScene(AFKMode.CurrentScene) end
	end)
	frame:SetScript("OnShow", function()
		local scene = AFKMode.CurrentScene
		if AFKMode.Model and scene then AFKMode.Model:SetAnimation(scene.animation or 96) end
	end)
	self.Frame = frame
	AddSpecialFrame("SlickUIAFKModeFrame")
end

function AFKMode:Initialize()
	self:CreateFrame()
	local events = CreateFrame("Frame")
	events:RegisterEvent("PLAYER_FLAGS_CHANGED")
	events:RegisterEvent("PLAYER_ENTERING_WORLD")
	events:RegisterEvent("DISPLAY_SIZE_CHANGED")
	events:RegisterEvent("UI_SCALE_CHANGED")
	events:SetScript("OnEvent", function(_, event, unit)
		if event == "DISPLAY_SIZE_CHANGED" or event == "UI_SCALE_CHANGED" then
			if AFKMode.CurrentScene then AFKMode:LayoutScene(AFKMode.CurrentScene) end
		elseif event ~= "PLAYER_FLAGS_CHANGED" or unit == "player" then
			C_Timer.After(0.1, function() AFKMode:Refresh() end)
		end
	end)
	self.EventFrame = events
	self:Refresh()
end

SlickUI:RegisterModule("AFKMode", AFKMode)
