local _, GF = ...

GF.WorkspaceRouter = {}
local Router = GF.WorkspaceRouter

local MEETING_STONE_TAB_ORDER = {
	GF.TAB_BROWSE,
	GF.TAB_CREATE,
	GF.TAB_BLOCKLIST,
	GF.TAB_SETTINGS,
	GF.TAB_DEBUG,
}

local MYTHIC_PLUS_TAB_ORDER = {
	GF.TAB_MPLUS_CHARACTER,
	GF.TAB_MPLUS_GROUP,
	GF.TAB_MPLUS_CARPOOL,
	GF.TAB_BROWSE,
	GF.TAB_CREATE,
	GF.TAB_MPLUS_DUNGEON,
	GF.TAB_BLOCKLIST,
	GF.TAB_SETTINGS,
	GF.TAB_DEBUG,
}

local ALL_TAB_IDS = {
	GF.TAB_BROWSE,
	GF.TAB_CREATE,
	GF.TAB_BLOCKLIST,
	GF.TAB_SETTINGS,
	GF.TAB_DEBUG,
	GF.TAB_MPLUS_CHARACTER,
	GF.TAB_MPLUS_GROUP,
	GF.TAB_MPLUS_CARPOOL,
	GF.TAB_MPLUS_DUNGEON,
}

local WORKSPACE_IDS = {
	GF.WORKSPACE_MEETING_STONE,
	GF.WORKSPACE_MYTHIC_PLUS,
}

local function copySequence(source)
	local copy = {}
	for index, value in ipairs(source) do
		copy[index] = value
	end
	return copy
end

local function makeTransition(previousWorkspaceID, previousTabID, workspaceID, tabID)
	local workspaceChanged = previousWorkspaceID ~= workspaceID
	local tabChanged = previousTabID ~= tabID
	return {
		previousWorkspaceID = previousWorkspaceID,
		previousTabID = previousTabID,
		workspaceID = workspaceID,
		tabID = tabID,
		workspaceChanged = workspaceChanged,
		tabChanged = tabChanged,
		changed = workspaceChanged or tabChanged,
	}
end

function Router:NormalizeWorkspaceID(workspaceID)
	local policy = GF.LFGWorkspacePolicy
	if policy and policy.NormalizeWorkspaceID then
		return policy:NormalizeWorkspaceID(workspaceID)
	end
	if workspaceID == GF.WORKSPACE_MYTHIC_PLUS then
		return GF.WORKSPACE_MYTHIC_PLUS
	end
	return GF.WORKSPACE_MEETING_STONE
end

function Router:GetAllTabIDs()
	return copySequence(ALL_TAB_IDS)
end

function Router:GetVisibleTabOrder(workspaceID)
	workspaceID = self:NormalizeWorkspaceID(
		workspaceID or self._workspaceID)
	if workspaceID == GF.WORKSPACE_MYTHIC_PLUS then
		return copySequence(MYTHIC_PLUS_TAB_ORDER)
	end
	return copySequence(MEETING_STONE_TAB_ORDER)
end

function Router:GetDefaultTabID(workspaceID)
	workspaceID = self:NormalizeWorkspaceID(workspaceID)
	if workspaceID == GF.WORKSPACE_MYTHIC_PLUS then
		return GF.TAB_MPLUS_CHARACTER
	end
	return GF.TAB_BROWSE
end

function Router:IsTabAvailable(tabID, workspaceID)
	if tabID == nil then
		return false
	end
	local found = false
	for _, candidateID in ipairs(self:GetVisibleTabOrder(workspaceID)) do
		if candidateID == tabID then
			found = true
			break
		end
	end
	if not found then
		return false
	end
	if tabID == GF.TAB_DEBUG then
		if self._debugTabAvailable == false then
			return false
		end
		return GF.Debug ~= nil
			and GF.Debug.IsDebugModeEnabled ~= nil
			and GF.Debug:IsDebugModeEnabled() == true
	end
	if tabID == GF.TAB_BLOCKLIST then
		return self._blocklistTabAvailable ~= false
	end
	return true
end

function Router:ResolveTabID(workspaceID, requestedTabID, opts)
	opts = opts or {}
	workspaceID = self:NormalizeWorkspaceID(workspaceID)
	local candidates = { requestedTabID }
	if opts.keepCurrent == true then
		candidates[#candidates + 1] = opts.currentTabID or self._tabID
	else
		local memory = self._tabByWorkspace
		candidates[#candidates + 1] = memory and memory[workspaceID] or nil
	end
	candidates[#candidates + 1] = self:GetDefaultTabID(workspaceID)
	for _, tabID in ipairs(candidates) do
		if self:IsTabAvailable(tabID, workspaceID) then
			return tabID
		end
	end
	for _, tabID in ipairs(self:GetVisibleTabOrder(workspaceID)) do
		if self:IsTabAvailable(tabID, workspaceID) then
			return tabID
		end
	end
	return nil
end

function Router:Init(opts)
	opts = opts or {}
	if self._initialized == true then
		return self._workspaceID, self._tabID
	end
	self._tabByWorkspace = {
		[GF.WORKSPACE_MEETING_STONE] = GF.TAB_BROWSE,
		[GF.WORKSPACE_MYTHIC_PLUS] = GF.TAB_MPLUS_CHARACTER,
	}
	self._blocklistTabAvailable = true
	self._debugTabAvailable = nil
	local workspaceID = self:NormalizeWorkspaceID(
		opts.workspaceID or GF.WORKSPACE_DEFAULT)
	local tabID = self:ResolveTabID(workspaceID, opts.tabID)
	self._workspaceID = workspaceID
	self._tabID = tabID
	self._tabByWorkspace[workspaceID] = tabID
	self._initialized = true
	return workspaceID, tabID
end

function Router:IsInitialized()
	return self._initialized == true
end

function Router:GetWorkspaceID()
	if self._initialized ~= true then
		self:Init()
	end
	return self._workspaceID or GF.WORKSPACE_MEETING_STONE
end

function Router:GetCurrentTabID()
	if self._initialized ~= true then
		self:Init()
	end
	return self._tabID or GF.TAB_BROWSE
end

function Router:GetRememberedTabID(workspaceID)
	if self._initialized ~= true then
		self:Init()
	end
	workspaceID = self:NormalizeWorkspaceID(workspaceID)
	return self._tabByWorkspace[workspaceID]
end

function Router:SetWorkspace(workspaceID, opts)
	opts = opts or {}
	if self._initialized ~= true then
		self:Init({ workspaceID = workspaceID })
	end
	local previousWorkspaceID = self._workspaceID
	local previousTabID = self._tabID
	workspaceID = self:NormalizeWorkspaceID(workspaceID)
	if previousWorkspaceID ~= nil and previousTabID ~= nil then
		self._tabByWorkspace[previousWorkspaceID] = previousTabID
	end
	local tabID = self:ResolveTabID(workspaceID, opts.targetTabID, {
		keepCurrent = opts.keepCurrent == true,
		currentTabID = previousTabID,
	})
	self._workspaceID = workspaceID
	self._tabID = tabID
	self._tabByWorkspace[workspaceID] = tabID
	return tabID, makeTransition(
		previousWorkspaceID,
		previousTabID,
		workspaceID,
		tabID
	)
end

function Router:SelectTab(tabID)
	if self._initialized ~= true then
		self:Init()
	end
	local workspaceID = self._workspaceID
	if not self:IsTabAvailable(tabID, workspaceID) then
		return nil
	end
	local previousTabID = self._tabID
	self._tabID = tabID
	self._tabByWorkspace[workspaceID] = tabID
	return makeTransition(
		workspaceID,
		previousTabID,
		workspaceID,
		tabID
	)
end

local function fallbackForUnavailableTab(self, unavailableTabID, preferredTabID)
	if self._initialized ~= true then
		self:Init()
	end
	for _, workspaceID in ipairs(WORKSPACE_IDS) do
		if self._tabByWorkspace[workspaceID] == unavailableTabID then
			local fallback = preferredTabID
			if not self:IsTabAvailable(fallback, workspaceID) then
				fallback = self:ResolveTabID(workspaceID)
			end
			self._tabByWorkspace[workspaceID] = fallback
		end
	end
	if self._tabID ~= unavailableTabID then
		return nil
	end
	local previousWorkspaceID = self._workspaceID
	local previousTabID = self._tabID
	local tabID = self._tabByWorkspace[previousWorkspaceID]
	if not self:IsTabAvailable(tabID, previousWorkspaceID) then
		tabID = self:ResolveTabID(previousWorkspaceID)
	end
	self._tabID = tabID
	self._tabByWorkspace[previousWorkspaceID] = tabID
	return makeTransition(
		previousWorkspaceID,
		previousTabID,
		previousWorkspaceID,
		tabID
	)
end

function Router:SetBlocklistTabAvailable(available)
	if self._initialized ~= true then
		self:Init()
	end
	self._blocklistTabAvailable = available ~= false
	if self._blocklistTabAvailable then
		return nil
	end
	return fallbackForUnavailableTab(self, GF.TAB_BLOCKLIST)
end

function Router:SetDebugTabAvailable(available)
	if self._initialized ~= true then
		self:Init()
	end
	self._debugTabAvailable = available == true
	if self._debugTabAvailable then
		return nil
	end
	return fallbackForUnavailableTab(self, GF.TAB_DEBUG, GF.TAB_SETTINGS)
end

function Router:ResolveInitialOpenRoute(route)
	local workspaceID = type(route) == "table" and route.workspaceID or nil
	if workspaceID == nil then
		local database = GF.GetDB and GF.GetDB()
		workspaceID = database and database.workspaceMode or GF.WORKSPACE_DEFAULT
	end
	return {
		workspaceID = self:NormalizeWorkspaceID(workspaceID),
		tabID = type(route) == "table" and route.tabID or nil,
	}
end

function Router:IsRouteActive(route, workspaceID, tabID)
	route = type(route) == "table" and route or {}
	workspaceID = workspaceID or self:GetWorkspaceID()
	tabID = tabID or self:GetCurrentTabID()
	if route.workspaceID ~= nil
		and self:NormalizeWorkspaceID(workspaceID)
			~= self:NormalizeWorkspaceID(route.workspaceID)
	then
		return false
	end
	if route.tabID ~= nil and tabID ~= route.tabID then
		return false
	end
	return true
end

function Router:ApplyOpenRoute(route, opts, workspaceBar, tabBar)
	if type(route) ~= "table" then
		return false, false
	end
	opts = opts or {}
	if self._initialized ~= true then
		self:Init()
	end

	local transition
	if route.workspaceID ~= nil then
		local _, committed = self:SetWorkspace(route.workspaceID, {
			targetTabID = route.tabID,
		})
		transition = committed
	elseif route.tabID ~= nil then
		transition = self:SelectTab(route.tabID)
	else
		return false, false
	end
	if transition == nil then
		return false, false
	end

	local projected = false
	if transition.workspaceChanged then
		if workspaceBar and workspaceBar.Select then
			projected = workspaceBar:Select(transition.workspaceID, {
				silent = opts.silent == true,
				targetTabID = transition.tabID,
				_routerTransition = transition,
			}) == true
		end
	elseif route.tabID ~= nil and transition.tabChanged
		and tabBar and tabBar.Select
	then
		tabBar:Select(transition.tabID, {
			skipDeferredLayout = opts.silent == true,
			_routerTransition = transition,
		})
		projected = true
	end
	return transition.changed, projected
end
