local Links = LibStub("AceAddon-3.0"):NewAddon("Links", "AceConsole-3.0", "AceHook-3.0", "AceEvent-3.0", "AceConsole-3.0")

local L = LibStub("AceLocale-3.0"):GetLocale("Links")

local options = {
	desc = L["Links - plenty of them"],
	type = "group",
	childGroups = "tab",
	args = {
		options = {
			order = 1,
			name = L["Options"],
			type = "group",
			args = {
				modinvert = {
					width = "full",
					name = L["modinvert"],
					desc = L["Invert the alt-clic modifier behavior of Links."],
					type = "toggle",
					get = function() return Links.db.profile.modinvert end,
					set = function(_, value) Links.db.profile.modinvert = value end,
				},
				autohide = {
					width = "full",
					name = L["autohide"],
					desc = L["Automatically hide tooltip on escape."],
					type = "toggle",
					get = function() return Links.db.profile.autohide end,
					set = function(_, value) Links:SetAutoHide(value) end,
				},
				autocomp = {
					width = "full",
					name = L["autocomp"],
					desc = L["Automatically show the compare window when hovering tooltips."],
					type = "toggle",
					get = function() return Links.db.profile.autocomp end,
					set = function(_, value) Links.db.profile.autocomp = value end,
				},
				showicon = {
					width = "full",
					name = L["showicon"],
					desc = L["Show item icon with the tooltip."],
					type = "toggle",
					get = function() return Links.db.profile.showicon end,
					set = function(_, value) Links.db.profile.showicon = value end,
				},
				history = {
					width = "full",
					name = L["Link history"],
					desc = L["Keep history of opened links."],
					type = "toggle",
					get = function() return Links.db.profile.history end,
					set = function(_, value)
						Links.db.profile.history = value
						Links.menu = nil
						Links.linkHistory = nil
					end,
				},
				types = {
					width = "full",
					name = L["Link types"],
					type = "multiselect",
					values = {
						unit = L["Unit"],
						item = L["Item"],
						quest = L["Quest"],
						spell = L["Spell"],
						enchant = L["Enchantments and Crafts"],
						achievement = L["Achievements"],
					},
					get = function (_, type) return Links.db.profile.types[type] end,
					set = function (_, type, value) Links.db.profile.types[type] = value end,
				}
			},
		},
	},
}

local defaults = {
	profile = {
		modinvert = false,
		autohide = true,
		autocomp = false,
		showicon = false,
		history = false,
		maxhistory = 25,
		positions = {},
		types = {
			unit = false,
			item = true,
			spell = true,
			enchant = true,
			quest = false,
			achievement = true,
		}
	}
}

function Links:SavePosition(frame)
	local index = frame.index
	local position = self.db.profile.positions[index]
	if not position then
		position = {}
		self.db.profile.positions[index] = position
	end
	position.left, position.top = frame:GetLeft(), frame:GetTop()
end

function Links:OnInitialize()
	self.frames = {}
	self.index = 1

	self.db = LibStub("AceDB-3.0"):New("LinksDB", defaults)

	local AceDBOptions = LibStub("AceDBOptions-3.0", true)
	if AceDBOptions then
		options.args.profiles = AceDBOptions:GetOptionsTable(self.db)
		options.args.profiles.order = 200
	end

	LibStub("AceConfig-3.0"):RegisterOptionsTable("Links", options)
	self.optionsFrame = LibStub("AceConfigDialog-3.0"):AddToBlizOptions("Links", "Links")
	self:RegisterChatCommand("links", "OnChatCommand")
end

function Links:OnChatCommand(input)
	if not input or input:trim() == "" then
		InterfaceOptionsFrame_OpenToCategory(self.optionsFrame)
	else
		LibStub("AceConfigCmd-3.0").HandleCommand(self, "links", "Links", input)
	end
end

Links.functionNames = {}
Links.functionHandlers = {}

function Links:AddToMenu(name, handler)
	local names, handlers = self.functionNames, self.functionHandlers
	names[#names + 1] = name
	handlers[#handlers + 1] = function (frame) handler(frame, frame.text, frame.link) end
end

Links:AddToMenu(L["Link in chat"], function (_, text)
	if not ChatFrameEditBox:IsVisible() then
		ChatFrame_OpenChat (text)
	else
		ChatFrameEditBox:Insert(text)
	end
	Links.menu = nil
end)

Links:AddToMenu(L["Dressup"], function (_, _, link)
	DressUpItemLink(link)
end)

Links:AddToMenu(L["Close all Tooltips"], function ()
	Links:HideAllFrames()
end)

Links:AddToMenu(L["Options"], function ()
	InterfaceOptionsFrame_OpenToCategory(Links.optionsFrame)
end)

function Links:Cartographer_InstanceLoot_DropClick(_, id)
	local name, link = GetItemInfo(id)
	if not name then
		local cil = Cartographer_InstanceLoot
		self.hooks[cil].DropClick(cil, id)
	elseif not HandleModifiedItemClick(link) then
		self:OpenLink(nil, link)
	end
end

function Links:OnEnable()
	CombatLog_LoadUI()
	self:RawHook("SetItemRef", true)
	local cil = Cartographer_InstanceLoot
	if cil then
		self:Hook(cil, "DropClick", "Cartographer_InstanceLoot_DropClick")
	end
end

function Links:SetAutoHide(value)
	local UISpecialFrames = UISpecialFrames
	self.db.profile.autohide = value
	for frame in pairs(self.frames) do
		local name = frame:GetName()
		if value then
			UISpecialFrames[#UISpecialFrames + 1] = name
		else
			for i = 1, #UISpecialFrames do
				if name == UISpecialFrames[i] then
					table.remove(UISpecialFrames, i)
					break
				end
			end
		end
	end
end

function Links:MakeMenu(frame)
	local menu = self.menu
	if not menu then
		menu = {}
		if self.db.profile.history then
			if not self.linkHistory then
				self.linkHistory = {}
			end
			menu[#menu + 1] = {
				text = L["Link history"],
				hasArrow = true,
				menuList = self.linkHistory
			}
		end
		for i, name in ipairs(self.functionNames) do
			menu[#menu + 1] = {
				text = name,
				func = self.functionHandlers[i],
				arg1 = frame,
			}
		end
		self.menu = menu
	else
		for _, entry in ipairs(menu) do
			entry.arg1 = frame
		end
	end
	return menu
end

function Links:OpenMenu(frame)
	local dropdown = self.dropdown
	if not dropdown then
		dropdown = CreateFrame("Frame", "LinksDropDown", nil, "UIDropDownMenuTemplate")
		dropdown.point = "TOPLEFT"
		dropdown.relativePoint = "TOPRIGHT"
		dropdown.displayMode = "MENU"
		self.dropdown = dropdown
	end
	dropdown.relativeTo = frame

	EasyMenu(self:MakeMenu(frame), dropdown)
end

function Links:HideAllFrames()
	for frame in pairs(self.frames) do
		if frame:IsVisible() then
			frame:Hide()
		end
	end
end

Links.NORMAL_COLOR = { 0.09, 0.12, 0.09 }
Links.ANCHORED_COLOR = { 0.18, 0.09, 0.09 }

local LinksFrame_Events = {
	OnDragStart = function (self)
		self:StartMoving()
	end,
	OnDragStop = function (self)
		self:StopMovingOrSizing()
		Links:ValidateFramePosition(self)
	end,
	OnHide = function (self)
		local left, top = self:GetLeft(), self:GetTop()
		self:ClearAllPoints()
		self:SetPoint("TOPLEFT", left, top - UIParent:GetHeight())
		self:SetBackdropColor(unpack(Links.NORMAL_COLOR))
		self.link = nil
		self.text = nil
		Links:SavePosition(self)
		Links:OnHideTooltip(self)
	end,
	OnReceiveDrag = function (self)
		local type, id, link = GetCursorInfo()
		if type == "item" then
			Links:OpenLink(nil, link, self)
			ClearCursor()
		end
	end,
	OnEnter = function (self)
		local autocomp = Links.db.profile.autocomp
		if autocomp or IsModifiedClick("COMPAREITEMS") then
			Links:ShowCompareItem(self)
		end
		if not autocomp then
			self:RegisterEvent("MODIFIER_STATE_CHANGED")
		end
	end,
	OnLeave = function (self)
		Links:HideCompareItems(self)
		self:UnregisterEvent("MODIFIER_STATE_CHANGED")
	end,
	OnEvent = function (self, event)
		if event == "MODIFIER_STATE_CHANGED" then
			if IsModifiedClick("COMPAREITEMS") then
				Links:ShowCompareItem(self)
			else
				Links:HideCompareItems(self)
			end
		end
	end,
}

local ShoppingTooltip1, ShoppingTooltip2 = ShoppingTooltip1, ShoppingTooltip2
function Links:ShowCompareItem(frame)
	local _, link = frame:GetItem()
	if not link then return end

	local item2
	if ShoppingTooltip1:SetHyperlinkCompareItem(link, 1) then
		if ShoppingTooltip2:SetHyperlinkCompareItem(link, 2) then
			item2 = true
		end
	else
		return
	end
	local left, right = frame:GetLeft(), frame:GetRight()
	local screen = GetScreenWidth()
	local is_left_side = (screen - right) < left
	local anchor = frame:GetAnchorType()
	if anchor then
		local width = ShoppingTooltip1:GetWidth() +
			(item2 and ShoppingTooltip2:GetWidth() or 0)

		if is_left_side then
			local delta = width - left
			if delta > 0 then
				frame:SetAnchorType(anchor, delta, 0)
			end
		else
			local delta = screen - (right + width)
			if delta < 0 then
				frame:SetAnchorType(anchor, delta, 0)
			end
		end
	end

	ShoppingTooltip1:SetOwner(frame, "ANCHOR_NONE")
	ShoppingTooltip1:ClearAllPoints()
	if is_left_side then
		ShoppingTooltip1:SetPoint("TOPRIGHT", frame, "TOPLEFT", 0, -10)
	else
		ShoppingTooltip1:SetPoint("TOPLEFT", frame, "TOPRIGHT", 0, -10)
	end
	ShoppingTooltip1:SetHyperlinkCompareItem(link, 1)
	ShoppingTooltip1:Show()

	if item2 then
		ShoppingTooltip2:SetOwner(ShoppingTooltip1, "ANCHOR_NONE")
		ShoppingTooltip2:ClearAllPoints()
		if is_left_side then
			ShoppingTooltip2:SetPoint("TOPRIGHT", ShoppingTooltip1, "TOPLEFT", 0, 0)
		else
			ShoppingTooltip2:SetPoint("TOPLEFT", ShoppingTooltip1, "TOPRIGHT", 0, 0)
		end
		ShoppingTooltip2:SetHyperlinkCompareItem(link, 2)
		ShoppingTooltip2:Show()
	end
end

function Links:HideCompareItems(frame)
	if ShoppingTooltip1:IsOwned(frame) then
		ShoppingTooltip1:Hide()
		ShoppingTooltip2:Hide()
	end
end

function Links:MakeFrame()
	local name = string.format("LinksTooltip%d", self.index)
	local frame = CreateFrame("GameTooltip", name, UIParent, "GameTooltipTemplate")
	frame.index = self.index

	self.index = self.index + 1
	self.frames[frame] = true

	local close = CreateFrame("Button", name.."Close", frame)
	frame.close = close

	local makeTexture = function (path, blend)
		local t = close:CreateTexture()
		t:SetAllPoints(close)
		t:SetTexture(path)
		if blend then
			t:SetBlendMode(blend)
		end
		return t
	end

	close:SetNormalTexture(makeTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Up"))
	close:SetPushedTexture(makeTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Down"))
	close:SetHighlightTexture(makeTexture("Interface\\Buttons\\UI-Panel-MinimizeButton-Highlight", "ADD"))

	close:RegisterForClicks("LeftButtonUp", "RightButtonUp")
	close:SetScript("OnClick",
		function (_, button)
			if button == "LeftButton" then
				frame:Hide()
			else
				self:OpenMenu(frame)
			end
		end)


	close:SetWidth(32)
	close:SetHeight(32)
	close:ClearAllPoints()
	close:SetPoint("TOPRIGHT", 1, 0)

	close:Show()

	frame:EnableMouse(true)
	frame:SetMovable(true)
	frame:SetToplevel(true)
	frame:SetFrameStrata("HIGH")
	local position = self.db.profile.positions[frame.index]
	if position then
		frame:SetPoint("TOPLEFT", position.left, position.top - UIParent:GetHeight())
	else
		frame:SetPoint("CENTER")
	end

	frame:SetPadding(16)
	frame:RegisterForDrag("LeftButton")
	frame:SetBackdropColor(unpack(self.NORMAL_COLOR))

	for event, handler in pairs(LinksFrame_Events) do
		frame:SetScript(event, handler)
	end

	if self.db.profile.autohide then
		local UISpecialFrames = UISpecialFrames
		UISpecialFrames[#UISpecialFrames + 1] = name
	end

	local Mendeleev = Mendeleev
	if Mendeleev and Mendeleev.OnTooltipSetItem then
		Mendeleev:HookScript(frame, "OnTooltipSetItem")
		Mendeleev:HookScript(frame, "OnTooltipCleared")
	end

	local Ratings = Ratings
	if Ratings then
		Ratings:HookTooltip(frame)
	end

	local Summary = Summary
	if Summary then
		Summary:HookTooltip(frame)
	end

	return frame
end

do
	local ANCHORING_DISTANCE = 10
	local abs = math.abs

	local function GetAnchorX(frame, other)
		local fleft, fright = frame:GetLeft(), frame:GetRight()
		local oleft, oright = other:GetLeft(), other:GetRight()

		if abs(fleft - oleft) < ANCHORING_DISTANCE then
			return "LEFT", "LEFT"
		elseif abs(fleft - oright) < ANCHORING_DISTANCE then
			return "LEFT", "RIGHT"
		elseif abs(fright - oright) < ANCHORING_DISTANCE then
			return "RIGHT", "RIGHT"
		elseif abs(fright - oleft) < ANCHORING_DISTANCE then
			return "RIGHT", "LEFT"
		end
	end

	local function GetAnchorY(frame, other)
		local ftop, fbottom = frame:GetTop(), frame:GetBottom()
		local otop, obottom = other:GetTop(), other:GetBottom()

		if abs(ftop - otop) < ANCHORING_DISTANCE then
			return "TOP", "TOP"
		elseif abs(ftop - obottom) < ANCHORING_DISTANCE then
			return "TOP", "BOTTOM"
		elseif abs(fbottom - obottom) < ANCHORING_DISTANCE then
			return "BOTTOM", "BOTTOM"
		elseif abs(fbottom - otop) < ANCHORING_DISTANCE then
			return "BOTTOM", "TOP"
		end
	end

	function Links:IsAnchoring(frame, other)
		local fanchorx, oanchorx = GetAnchorX(frame, other)
		if not fanchorx then return end
		local fanchory, oanchory = GetAnchorY(frame, other)
		if not fanchory then return end

		if (fanchorx == oanchorx) == (fanchory == oanchory) then return end

		return fanchory..fanchorx, oanchory..oanchorx, 0, 0
	end
end

function Links:OnHideTooltip(frame)
	for other in pairs(self.frames) do
		if other.anchored == frame then
			local left, top = other:GetLeft(), other:GetTop()
			other:ClearAllPoints()
			other:SetPoint("TOPLEFT", left, top - UIParent:GetHeight())
			other:SetBackdropColor(unpack(self.NORMAL_COLOR))
			other.anchored = nil
			self:SavePosition(other)
		end
	end
end

function Links:ValidateFramePosition(frame)
	-- check anchoring
	for other in pairs(self.frames) do
		if other ~= frame and other:IsVisible() and not other.anchored then
			local point, relative, x, y = self:IsAnchoring(frame, other)
			if point then
				frame:ClearAllPoints()
				frame:SetBackdropColor(unpack(self.ANCHORED_COLOR))
				frame:SetPoint(point, other, relative, x, y)
				frame.anchored = other
				return
			end
		end
	end
	local left, top = frame:GetLeft(), frame:GetTop()
	frame:ClearAllPoints()
	frame:SetPoint("TOPLEFT", left, top-UIParent:GetHeight())
	frame:SetBackdropColor(unpack(self.NORMAL_COLOR))
	frame.anchored = nil
	ValidateFramePosition(frame)
	self:SavePosition(frame)
end

function Links:GetAvailableFrame()
	for frame in pairs(self.frames) do
		if not frame:IsVisible() then
			return frame
		end
	end
	return self:MakeFrame()
end

do
	local event
	function Links:TriggerEvent(link, frame)
		if event == nil then
			event = AceLibrary and AceLibrary("AceEvent-2.0") or false
		end
		if event then
			event:TriggerEvent("Links_LinkOpened", link, frame)
		end
		self:SendMessage("Links_LinkOpened", link, frame)
	end
end

function Links:OpenLink(link, text, frame)
	link = link or (text and text:match("|H([^|]+)|h"))
	if not link then return end

	frame = frame or self:GetAvailableFrame()
	frame:ClearLines()
	frame:SetOwner(UIParent, "ANCHOR_PRESERVE")
	frame:SetHyperlink(link)
	frame.link = link
	frame.text = text

	local icon = frame.icon
	if self.db.profile.showicon and link:sub(1, 4) == "item" then
		if not icon then
			icon = frame:CreateTexture(nil, "ARTWORK")
			frame.icon = icon
			icon:SetHeight(36)
			icon:SetWidth(36)
			icon:ClearAllPoints();
			icon:SetPoint("TOPRIGHT", frame, "TOPLEFT", "1", "-2")
		end
		icon:SetTexture(GetItemIcon(link))
		icon:Show()
	elseif icon then
		icon:Hide()
	end

	self:InsertInLinksHistory(link, text)
	self:TriggerEvent(link, frame)
	frame:Show()
end

function Links:InsertInLinksHistory(link, text)
	local p = self.db.profile
	if p.history then
		local h = self.linkHistory
		if not h then
			h = {}
			self.linkHistory = h
		end
		local prev
		for i, entry in ipairs(h) do
			if entry.arg1 == link then
				prev = table.remove(h, i)
				break
			end
		end
		if not prev then
			prev = h[p.maxhistory] or { func = SetItemRef }
			prev.text = text
			prev.arg1 = link
			prev.arg2 = text
		end
		table.insert(h, 1, prev)
		h[p.maxhistory + 1] = nil
	end
end

function Links:SetItemRef(link, text, button)
	local type = strsplit(":", link)
	local v = self.db.profile.types[type]
	if v == nil then
		return self.hooks.SetItemRef(link, text, button)
	elseif not self.db.profile.types[type] then
		return self.hooks.SetItemRef(link, text, button)
	end
	if not self.db.profile.modinvert == not not IsModifiedClick("Links_Disable") then
		if not HandleModifiedItemClick(text) then
			if type == "player" then
				return self.hooks.SetItemRef(link, text, button)
			else
				ShowUIPanel(ItemRefTooltip)
				if not ItemRefTooltip:IsShown() then
					ItemRefTooltip:SetOwner(UIParent, "ANCHOR_PRESERVE")
				end
				ItemRefTooltip:SetHyperlink(link)
			end
		end
	elseif not HandleModifiedItemClick(text) then
		for frame in pairs(self.frames) do
			if frame:IsVisible() and frame.link == link then
				frame:Hide()
				frame.link = nil
				return
			end
		end
		self:OpenLink(link, text)
	end
end

_G.Links = Links
