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

local Fishing = {
	Session = {},
	SessionEntries = {},
	SessionTotal = 0,
	SessionSeconds = 0,
	ActiveStartedAt = nil,
	StopGeneration = 0,
	ArmedUntil = 0,
	SeenLootSlots = {},
}
SlickUI.Fishing = Fishing

local FISHING_SPELL_ID = 131474
local CATCH_SOUND = SOUNDKIT and (SOUNDKIT.IG_MAINMENU_OPTION_CHECKBOX_ON or SOUNDKIT.IG_MAINMENU_OPTION) or 856

local function FishingLureTooltipInfo(bagID, slotID)
	if not C_TooltipInfo or not C_TooltipInfo.GetBagItem then return false, 0 end
	local data = C_TooltipInfo.GetBagItem(bagID, slotID)
	local identified, bonus = false, 0
	for _, line in ipairs((data and data.lines) or {}) do
		local text = line.leftText and line.leftText:lower()
		local namesLure = text and (text:find("%f[%a]lure%f[%A]") or text:find("%f[%a]bait%f[%A]"))
		local appliesToPole = text and text:find("fishing pole", 1, true)
		local increasesFishing = text and text:find("increases fishing", 1, true)
		if text and (namesLure or appliesToPole or increasesFishing) then
			identified = true
		end
		local amount = text and (text:match("increases fishing by%s+(%d+)") or text:match("%+(%d+)%s+fishing"))
		bonus = math.max(bonus, tonumber(amount) or 0)
	end
	return identified, bonus
end

local function Settings()
	return SlickUI.DB and SlickUI.DB.fishing
end

local function CharacterKey()
	return (UnitName("player") or "Unknown") .. " - " .. (GetRealmName() or "Unknown Realm")
end

local function Location()
	local mapID = C_Map.GetBestMapForUnit("player") or 0
	local zone = GetZoneText() or "Unknown Zone"
	local subzone = GetSubZoneText()
	if subzone == "" then subzone = nil end
	return mapID, zone, subzone
end

function Fishing:GetSessionTotal()
	return self.SessionTotal
end

function Fishing:GetSessionSeconds()
	return self.SessionSeconds + (self.ActiveStartedAt and math.max(0, GetTime() - self.ActiveStartedAt) or 0)
end

function Fishing:GetLifetimeSeconds(characterOnly)
	local settings = Settings()
	if not settings then return 0 end
	local saved = characterOnly and ((settings.characterSeconds or {})[CharacterKey()] or 0) or (settings.lifetimeSeconds or 0)
	return saved + (self.ActiveStartedAt and math.max(0, GetTime() - self.ActiveStartedAt) or 0)
end

function Fishing:StartActiveTime()
	self.StopGeneration = self.StopGeneration + 1
	if not self.ActiveStartedAt then self.ActiveStartedAt = GetTime() end
end

function Fishing:CommitActiveTime()
	if not self.ActiveStartedAt then return end
	local elapsed = math.max(0, GetTime() - self.ActiveStartedAt)
	self.ActiveStartedAt = nil
	self.SessionSeconds = self.SessionSeconds + elapsed
	local settings = Settings()
	if settings then
		settings.lifetimeSeconds = (settings.lifetimeSeconds or 0) + elapsed
		settings.characterSeconds = settings.characterSeconds or {}
		local character = CharacterKey()
		settings.characterSeconds[character] = (settings.characterSeconds[character] or 0) + elapsed
	end
end

function Fishing:SchedulePause()
	self.StopGeneration = self.StopGeneration + 1
	local generation = self.StopGeneration
	C_Timer.After(10, function()
		if Fishing.StopGeneration == generation then
			Fishing:CommitActiveTime()
			if SlickUI.FishingLedger then SlickUI.FishingLedger:Refresh() end
		end
	end)
end

function Fishing:HasPoleLure()
	if not GetWeaponEnchantInfo then return false end
	local hasMainHandEnchant = GetWeaponEnchantInfo()
	return hasMainHandEnchant == true
end

function Fishing:FindAvailableLure()
	local best
	for bagID = Enum.BagIndex.Backpack, Enum.BagIndex.ReagentBag do
		for slotID = 1, (C_Container.GetContainerNumSlots(bagID) or 0) do
			local info = C_Container.GetContainerItemInfo(bagID, slotID)
			local useSpell = info and info.itemID and C_Item.GetItemSpell(info.itemID)
			if useSpell then
				local identified, bonus = FishingLureTooltipInfo(bagID, slotID)
				bonus = tonumber(bonus) or 0
				if identified and (not best or bonus > (best.bonus or 0)) then
					best = { itemID = info.itemID, link = info.hyperlink, icon = info.iconFileID, count = info.stackCount or 1, bonus = bonus, bagID = bagID, slotID = slotID }
				end
			end
		end
	end
	if best then return best.itemID, best.link, best.icon, best.count, best.bonus, best.bagID, best.slotID end
end

function Fishing:CheckLureOnCast()
	local itemID, link = self:FindAvailableLure()
	if not self:HasPoleLure() and itemID then
		local now = GetTime()
		if now >= (self.NextLureWarning or 0) then
			SlickUI:Print("No fishing lure is applied. " .. (link or "A fishing lure") .. " is available in your bags; open the Fishing Ledger to apply it.")
			self.NextLureWarning = now + 60
		end
	end
	if SlickUI.FishingLedger then SlickUI.FishingLedger:RefreshLure() end
end

function Fishing:GetAllTimeTotal(characterOnly, mapOnly)
	local total, character = 0, CharacterKey()
	local mapID = C_Map.GetBestMapForUnit("player") or 0
	for _, entry in ipairs((Settings() and Settings().entries) or {}) do
		if (not characterOnly or entry.character == character) and (not mapOnly or entry.mapID == mapID) then
			total = total + (entry.quantity or 0)
		end
	end
	return total
end

function Fishing:Record(itemLink, quantity)
	local settings = Settings()
	if not settings or settings.enabled == false then return end
	local itemID = itemLink and C_Item.GetItemInfoInstant(itemLink)
	if not itemID then return end
	quantity = math.max(1, tonumber(quantity) or 1)
	local mapID, zone, subzone = Location()
	local entry = {
		itemID = itemID,
		link = itemLink,
		quantity = quantity,
		character = CharacterKey(),
		mapID = mapID,
		zone = zone,
		subzone = subzone,
		timestamp = GetServerTime and GetServerTime() or time(),
	}
	table.insert(settings.entries, 1, entry)
	while #settings.entries > (settings.maxEntries or 5000) do table.remove(settings.entries) end
	self.Session[itemID] = (self.Session[itemID] or 0) + quantity
	table.insert(self.SessionEntries, 1, entry)
	self.SessionTotal = self.SessionTotal + quantity
	if SlickUI.FishingLedger and SlickUI.FishingLedger.Window and SlickUI.FishingLedger.Window:IsShown() then
		SlickUI.FishingLedger:Refresh()
	end
	if settings.showIndicator and self.Indicator then
		self.Indicator.Text:SetText(string.format("Caught %s x%d", itemLink, quantity))
		self.Indicator:Show()
		C_Timer.After(3, function() if Fishing.Indicator then Fishing.Indicator:Hide() end end)
	end
	if settings.playCatchSound then PlaySound(CATCH_SOUND, "Master") end
	if SlickUI.InformationBars then SlickUI.InformationBars:UpdateBlocks() end
end

function Fishing:GetBarText()
	return string.format("Fishing: |cff55bfff%d|r session  |cff42df72%d|r all time", self.SessionTotal, self:GetAllTimeTotal(true, false))
end

function Fishing:BuildTooltip(tooltip)
	tooltip:AddLine("Fishing Ledger", 1, 0.76, 0.30)
	tooltip:AddDoubleLine("This session", tostring(self.SessionTotal), 1, 1, 1, 0.33, 0.75, 1)
	tooltip:AddDoubleLine("Current character", tostring(self:GetAllTimeTotal(true, false)), 1, 1, 1, 0.26, 0.87, 0.45)
	tooltip:AddDoubleLine("Current zone", tostring(self:GetAllTimeTotal(true, true)), 1, 1, 1, 0.26, 0.87, 0.45)
	tooltip:AddLine(" ")
	tooltip:AddLine("Click to open the Fishing Ledger.", 0.65, 0.68, 0.75)
end

function Fishing:CaptureLoot()
	if GetTime() > self.ArmedUntil then return end
	for slot = 1, GetNumLootItems() do
		if not self.SeenLootSlots[slot] then
			local link = GetLootSlotLink(slot)
			if link then
				local _, _, quantity = GetLootSlotInfo(slot)
				self.SeenLootSlots[slot] = true
				self:Record(link, quantity)
			end
		end
	end
end

function Fishing:CreateIndicator()
	if self.Indicator then return end
	local frame = CreateFrame("Frame", "SlickUIFishingIndicator", UIParent, "BackdropTemplate")
	frame:SetSize(390, 58)
	frame:SetPoint("TOP", 0, -155)
	frame:SetFrameStrata("HIGH")
	frame:SetBackdrop({ bgFile = "Interface\\Buttons\\WHITE8X8", edgeFile = "Interface\\Buttons\\WHITE8X8", edgeSize = 2 })
	frame:SetBackdropColor(0.01, 0.02, 0.035, 0.92)
	frame:SetBackdropBorderColor(0.1, 0.65, 1, 1)
	local text = frame:CreateFontString(nil, "ARTWORK", "GameFontNormalLarge")
	text:SetPoint("CENTER")
	text:SetText("Fishing cast active")
	frame.Text = text
	frame:Hide()
	self.Indicator = frame
end

function Fishing:OpenLedger()
	local loaded, reason = C_AddOns.LoadAddOn("SlickUI_Fishing")
	if not loaded and reason ~= "ALREADY_LOADED" then
		SlickUI:Print("Fishing Ledger could not load: " .. tostring(reason))
		return
	end
	if SlickUI.FishingLedger then SlickUI.FishingLedger:Show() end
end

function Fishing:Initialize()
	self:CreateIndicator()
	local events = CreateFrame("Frame")
	events:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED")
	events:RegisterEvent("UNIT_SPELLCAST_CHANNEL_START")
	events:RegisterEvent("UNIT_SPELLCAST_CHANNEL_STOP")
	events:RegisterEvent("LOOT_READY")
	events:RegisterEvent("LOOT_CLOSED")
	events:SetScript("OnEvent", function(_, event, unit, _, spellID)
		if event == "UNIT_SPELLCAST_SUCCEEDED" or event == "UNIT_SPELLCAST_CHANNEL_START" then
			if unit == "player" and spellID == FISHING_SPELL_ID then
				Fishing:StartActiveTime()
				Fishing:CheckLureOnCast()
				Fishing.ArmedUntil = GetTime() + 35
				wipe(Fishing.SeenLootSlots)
				if Settings().showIndicator then
					Fishing.Indicator.Text:SetText("Fishing cast active")
					Fishing.Indicator:Show()
				end
			end
		elseif event == "LOOT_READY" then
			Fishing:CaptureLoot()
		elseif event == "LOOT_CLOSED" then
			Fishing.ArmedUntil = 0
			wipe(Fishing.SeenLootSlots)
			Fishing.Indicator:Hide()
			Fishing:SchedulePause()
		elseif event == "PLAYER_LOGOUT" then
			Fishing:CommitActiveTime()
		elseif event == "UNIT_SPELLCAST_CHANNEL_STOP" and unit == "player" then
			-- Do not disarm here: loot can arrive immediately after the channel ends.
			if GetTime() > Fishing.ArmedUntil then Fishing.Indicator:Hide() end
		end
	end)
	events:RegisterEvent("PLAYER_LOGOUT")
	self.EventFrame = events
end

SlickUI:RegisterModule("Fishing", Fishing)
