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

local FlightTimer = {}
SlickUI.FlightTimer = FlightTimer

local function FormatTime(seconds)
	seconds = math.max(0, math.floor((seconds or 0) + 0.5))
	return string.format("%d:%02d", math.floor(seconds / 60), seconds % 60)
end

local function RouteKey(source, destination)
	return (source or "?") .. "\031" .. (destination or "?")
end

function FlightTimer:GetEstimate(source, destination)
	local route = SlickUI.DB.flightTimer.routes[RouteKey(source, destination)]
	return route and route.seconds
end

function FlightTimer:GetRouteCount()
	local count = 0
	for _, route in pairs(SlickUI.DB.flightTimer.routes or {}) do
		if type(route) == "table" and type(route.seconds) == "number" then count = count + 1 end
	end
	return count
end

function FlightTimer:BuildExportText()
	local entries = {}
	for key, route in pairs(SlickUI.DB.flightTimer.routes or {}) do
		if type(key) == "string" and type(route) == "table" and type(route.seconds) == "number" then
			entries[#entries + 1] = {
				key = key,
				seconds = route.seconds,
				samples = math.max(1, math.floor(tonumber(route.samples) or 1)),
			}
		end
	end
	table.sort(entries, function(left, right) return left.key < right.key end)
	local lines = {
		"-- SlickUI locally learned flight routes",
		"-- Generated " .. date("%Y-%m-%d %H:%M:%S"),
		"return {",
	}
	for _, entry in ipairs(entries) do
		lines[#lines + 1] = string.format("\t[%q] = { seconds = %.3f, samples = %d },", entry.key, entry.seconds, entry.samples)
	end
	lines[#lines + 1] = "}"
	return table.concat(lines, "\n")
end

function FlightTimer:StartFlight(source, destination)
	self.Active = { source = source or "Unknown", destination = destination or "Unknown", started = GetTime() }
	self.Active.estimate = self:GetEstimate(self.Active.source, self.Active.destination)
	self.Frame:SetShown(SlickUI.DB.flightTimer.enabled == true)
	self.Frame:SetScript("OnUpdate", function() FlightTimer:UpdateBar() end)
end

function FlightTimer:FinishFlight()
	if not self.Active then return end
	local elapsed = GetTime() - self.Active.started
	if elapsed > 10 then
		local key = RouteKey(self.Active.source, self.Active.destination)
		local old = SlickUI.DB.flightTimer.routes[key]
		local seconds = old and ((old.seconds * old.samples + elapsed) / (old.samples + 1)) or elapsed
		SlickUI.DB.flightTimer.routes[key] = { seconds = seconds, samples = (old and old.samples or 0) + 1 }
	end
	self.Active = nil; self.Frame:Hide(); self.Frame:SetScript("OnUpdate", nil)
end

function FlightTimer:UpdateBar()
	if not self.Active then return end
	local elapsed = GetTime() - self.Active.started
	local estimate = self.Active.estimate
	self.Frame.Route:SetText(self.Active.source .. "  >  " .. self.Active.destination)
	if estimate and estimate > 0 then
		self.Frame.Bar:SetMinMaxValues(0, estimate); self.Frame.Bar:SetValue(math.min(elapsed, estimate))
		self.Frame.Time:SetText(FormatTime(math.max(0, estimate - elapsed)) .. " remaining")
	else
		self.Frame.Bar:SetMinMaxValues(0, 1); self.Frame.Bar:SetValue(0)
		self.Frame.Time:SetText(FormatTime(elapsed) .. " - learning route")
	end
	if not UnitOnTaxi("player") and elapsed > 2 then self:FinishFlight() end
end

function FlightTimer:CreateFrame()
	local frame = CreateFrame("Frame", "SlickUIFlightTimer", UIParent)
	frame:SetSize(390, 48); frame:SetFrameStrata("MEDIUM"); frame:SetClampedToScreen(true); Theme:CreatePanel(frame)
	local route = Theme:CreateText(frame, 11); route:SetPoint("TOP", 0, -7); route:SetTextColor(unpack(Theme.Colors.goldText)); frame.Route = route
	local bar = CreateFrame("StatusBar", nil, frame); bar:SetPoint("BOTTOMLEFT", 7, 7); bar:SetPoint("BOTTOMRIGHT", -7, 7); bar:SetHeight(14); bar:SetStatusBarTexture(Theme.Texture); bar:SetStatusBarColor(0.15, 0.65, 0.95); frame.Bar = bar
	local time = Theme:CreateText(bar, 9); time:SetPoint("CENTER"); frame.Time = time
	self.Frame = frame; frame:Hide(); SlickUI.Movers:Register(frame, "flightTimer", "Flight Timer")
end

function FlightTimer:AttachMapCheckbox()
	if self.MapCheckbox then self.MapCheckbox:SetChecked(SlickUI.DB.flightTimer.showMapTimes); return end
	if not FlightMapFrame then return end
	local check = CreateFrame("CheckButton", "SlickUIFlightMapTimesCheckbox", FlightMapFrame, "UICheckButtonTemplate")
	check:SetPoint("TOPLEFT", FlightMapFrame, "TOPLEFT", 92, -36); check:SetSize(26, 26)
	check:SetFrameStrata("DIALOG"); check:SetFrameLevel((FlightMapFrame:GetFrameLevel() or 0) + 100)
	check.Text:SetText("Show flight times"); check.Text:SetTextColor(1, 0.82, 0)
	check:SetChecked(SlickUI.DB.flightTimer.showMapTimes)
	check:SetScript("OnClick", function(self) SlickUI.DB.flightTimer.showMapTimes = self:GetChecked() == true end)
	self.MapCheckbox = check
end

function FlightTimer:AddPinTime(pin)
	if not pin or pin.SlickUIFlightHooked then return end
	pin.SlickUIFlightHooked = true
	pin:HookScript("OnEnter", function(self)
		if not SlickUI.DB.flightTimer.showMapTimes or not GameTooltip:IsOwned(self) then return end
		local data = self.taxiNodeData or self.nodeData
		local destination = data and (data.name or data.nodeName)
		local source
		for index = 1, (NumTaxiNodes and NumTaxiNodes() or 0) do
			if TaxiNodeGetType(index) == "CURRENT" then source = TaxiNodeName(index); break end
		end
		if not destination then return end
		local estimate = FlightTimer:GetEstimate(source, destination)
		GameTooltip:AddLine(estimate and ("SlickUI flight time: " .. FormatTime(estimate)) or "SlickUI flight time: learning required", 0.35, 0.8, 1)
		GameTooltip:Show()
	end)
end

function FlightTimer:HookActivePins()
	if not FlightMapFrame or not FlightMapFrame.pinPools then return end
	for template, pool in pairs(FlightMapFrame.pinPools) do
		if type(template) == "string" and template:find("FlightPoint") and pool.EnumerateActive then
			for pin in pool:EnumerateActive() do self:AddPinTime(pin) end
		end
	end
end

function FlightTimer:AttachMapUI()
	self:AttachMapCheckbox()
	self:HookMapPins()
	self:HookActivePins()
end

function FlightTimer:QueueMapAttach()
	for _, delay in ipairs({ 0, 0.05, 0.2, 0.5, 1 }) do
		C_Timer.After(delay, function() FlightTimer:AttachMapUI() end)
	end
end

function FlightTimer:HookTaxi()
	if self.HookedTaxi or not TakeTaxiNode then return end
	self.HookedTaxi = true
	hooksecurefunc("TakeTaxiNode", function(slot)
		local source, destination
		for i = 1, (NumTaxiNodes and NumTaxiNodes() or 0) do
			local kind = TaxiNodeGetType(i)
			if kind == "CURRENT" then source = TaxiNodeName(i) end
			if i == slot then destination = TaxiNodeName(i) end
		end
		if destination then C_Timer.After(0.5, function() FlightTimer:StartFlight(source, destination) end) end
	end)
end

function FlightTimer:HookMapPins()
	if self.HookedPins or not FlightMap_FlightPointPinMixin then return end
	self.HookedPins = true
	hooksecurefunc(FlightMap_FlightPointPinMixin, "OnMouseEnter", function(pin)
		if not SlickUI.DB.flightTimer.showMapTimes or not GameTooltip:IsOwned(pin) then return end
		local destination = pin.taxiNodeData and pin.taxiNodeData.name
		local source
		for i = 1, (NumTaxiNodes and NumTaxiNodes() or 0) do if TaxiNodeGetType(i) == "CURRENT" then source = TaxiNodeName(i); break end end
		local estimate = FlightTimer:GetEstimate(source, destination)
		GameTooltip:AddLine(estimate and ("SlickUI flight time: " .. FormatTime(estimate)) or "SlickUI flight time: not learned", 0.35, 0.8, 1)
		GameTooltip:Show()
	end)
end

function FlightTimer:SetEnabled(enabled)
	SlickUI.DB.flightTimer.enabled = enabled == true
	if not enabled then self.Frame:Hide() elseif self.Active then self.Frame:Show() end
end

function FlightTimer:Initialize()
	self:CreateFrame(); self:HookTaxi(); self:QueueMapAttach()
	local events = CreateFrame("Frame")
	for _, event in ipairs({ "PLAYER_ENTERING_WORLD", "TAXIMAP_OPENED", "PLAYER_CONTROL_GAINED" }) do events:RegisterEvent(event) end
	events:SetScript("OnEvent", function(_, event)
		if event == "TAXIMAP_OPENED" then FlightTimer:QueueMapAttach()
		elseif event == "PLAYER_CONTROL_GAINED" and FlightTimer.Active and not UnitOnTaxi("player") then FlightTimer:FinishFlight() end
	end)
	self.EventFrame = events
end

SlickUI:RegisterModule("FlightTimer", FlightTimer)
