G_UIBase = require("core.ui.base.uiBase")
G_UIPageBase = require("core.ui.base.pageBase")
G_UIModuleBase = require("core.ui.base.moduleBase")
G_UIPartBase = require("core.ui.base.partBase")
G_EventManagerBase = require("core.ui.base.eventManagerBase")
C_GameObject = C_GameObject or CS.UnityEngine.GameObject
C_Canvas = C_Canvas or CS.UnityEngine.Canvas
C_CanvasScaler = C_CanvasScaler or CS.UnityEngine.UI.CanvasScaler
C_GraphicRaycaster = C_GraphicRaycaster or CS.UnityEngine.UI.GraphicRaycaster
C_CanvasGroup = C_CanvasGroup or CS.UnityEngine.CanvasGroup
C_BlendDefinition = C_BlendDefinition or CS.Lens.Framework.UI.LBlendDefinition
C_UIBinding = C_UIBinding or CS.Lens.Framework.UI.UIBinding
C_LButton = C_LButton or CS.Lens.Framework.UI.LButton
C_LToggle = C_LToggle or CS.Lens.Framework.UI.LToggle
C_LTabItem = C_LTabItem or CS.Lens.Framework.UI.LTabItem
C_LSlider = C_LSlider or CS.Lens.Framework.UI.LSlider
local L_UI = {}
L_UI._rootTrans = nil
L_UI._canvasScaler = nil
L_UI._auxiliaries = {}
L_UI._uiConfig = {}
L_UI._groupMode = {}
L_UI._eventCenter = require("core.event").new()
L_UI.groupKeys = {
  stack = "stack",
  order = "order",
  freedom = "freedom",
  unique = "unique"
}
L_UI.pageEvent = {
  reqOpen = "reqOpen",
  init = "init",
  preOpen = "preOpen",
  opened = "opened",
  reqClose = "reqClose",
  closed = "closed",
  hided = "hided",
  showed = "showed"
}
L_UI.audioEvent = {
  pageBgm = "panelBgmAudio",
  pageOpen = "panelOpenAudio",
  pageClose = "panelCloseAudio",
  pageState = "panelStateAudio"
}

function L_UI:_initUICamera(config)
  local uiCameraRoot = C_CameraManager.uiCamera.transform
  CS.UnityEngine.Object.DontDestroyOnLoad(uiCameraRoot.gameObject)
  local uiCamera = uiCameraRoot.gameObject:TryGetComponent(typeof(CS.UnityEngine.Camera))
  local urpCameraData = uiCameraRoot.gameObject:TryGetComponent(typeof(CS.UnityEngine.Rendering.Azur.UniversalAdditionalCameraData))
  uiCamera.clearFlags = CS.UnityEngine.CameraClearFlags.Depth
  uiCamera.depth = 1
  if config.cullingMask then
    uiCamera.cullingMask = config.cullingMask
  end
  if config.volumeMask ~= nil then
    local layerMask = urpCameraData.volumeLayerMask
    layerMask.value = self._uiConfig.config.volumeMask
    urpCameraData.volumeLayerMask = layerMask
  end
end

function L_UI:_initAuxiliary()
  for i = 1, #self._uiConfig.auxiliary do
    local value = self._uiConfig.auxiliary[i]
    local aux = {}
    aux.config = value
    local cls = require(value.luaPath).new()
    aux.cls = cls
    aux.auxiliaryName = value.auxiliaryName
    if cls.initialize then
      cls:initialize(value)
    end
    self._auxiliaries[value.auxiliaryName] = aux
  end
end

function L_UI:_initPages()
  self._groupMode[self.groupKeys.stack] = require("core.ui.uitype.stackType").new(self, self._uiConfig.config)
  self._groupMode[self.groupKeys.order] = require("core.ui.uitype.orderType").new(self, self._uiConfig.config)
  self._groupMode[self.groupKeys.freedom] = require("core.ui.uitype.freedomType").new(self, self._uiConfig.config)
  self._groupMode[self.groupKeys.unique] = require("core.ui.uitype.uniqueType").new(self, self._uiConfig.config)
end

function L_UI:_initPageCanvas(page)
  assert(page)
  local obj = page.gameObject
  local canvas = obj:TryGetComponent(typeof(C_Canvas))
  if not canvas then
    error("ui root must has Canvas component")
  else
    if page.config.planeDistance then
      canvas.planeDistance = page.config.planeDistance
    end
    if page.config.layer then
      canvas.sortingOrder = page.config.layer
      page.cls.layer = page.config.layer
    else
      canvas.sortingOrder = 1
    end
    page.cls.canvas = canvas
  end
end

function L_UI:_initPageGraphicRaycaster(page)
  assert(page)
  local obj = page.gameObject
  local graphicRaycaster = obj:TryGetComponent(typeof(C_GraphicRaycaster))
  if graphicRaycaster and not graphicRaycaster:IsNull() then
    page.cls.graphicRaycaster = graphicRaycaster
    page.graphicRaycaster = graphicRaycaster
  end
end

function L_UI:_initPageCanvasGroup(page)
  assert(page)
  if page.canvasGroup then
    return
  end
  local obj = page.gameObject
  local canvasGroup = obj:TryGetComponent(typeof(C_CanvasGroup))
  if canvasGroup and not canvasGroup:IsNull() then
    page.canvasGroup = canvasGroup
  end
end

function L_UI:_initPageBlendDefinition(page)
  assert(page)
  local obj = page.gameObject
  local blendDefinition = obj:TryGetComponent(typeof(C_BlendDefinition))
  if self._uiConfig.config.pageDefaultAnim and (blendDefinition == nil or blendDefinition:IsNull()) then
    blendDefinition = obj:AddComponent(typeof(C_BlendDefinition))
    self:execMiddleware("setupPageDefaultAnim", page, blendDefinition)
  end
  if blendDefinition and not blendDefinition:IsNull() then
    page.cls.blendDefinition = blendDefinition
  end
end

function L_UI:_onPageObjLoaded(page, obj, callback)
  if page.status == "closed" then
    if page.cls.loadHandle:IsValid() then
      C_LuaUtility.DestroyObject(page.cls.loadHandle)
    end
    if callback ~= nil then
      callback(nil)
    end
  else
    obj.name = page.pageName
    obj:SetActive(false)
    page.gameObject = obj
    local uiBinding
    if C_UIBinding then
      uiBinding = obj:TryGetComponent(typeof(C_UIBinding))
      if uiBinding then
      end
    end
    page.cls:setUIBinding(uiBinding)
    self:_initPageCanvas(page)
    self:_initPageBlendDefinition(page)
    self:_initPageGraphicRaycaster(page)
    self:_initPageCanvasGroup(page)
    if page.cls.created then
      page.cls:created(obj, page.config)
    end
    if callback ~= nil then
      callback(page)
    end
  end
end

function L_UI:newPage(pageName, options, callback)
  self:profilerMarkTimeForRecord(pageName, "t_newPageStart")
  local pageConfig = self._uiConfig.pages[pageName]
  local assetName
  if pageConfig.assetName then
    assetName = pageConfig.assetName .. ".prefab"
  elseif pageConfig.assetRelateName then
    assetName = self._uiConfig.prefix.assetPathPrefix .. pageConfig.assetRelateName .. ".prefab"
  else
    assetName = self._uiConfig.prefix.assetPathPrefix .. pageName .. ".prefab"
  end
  local cls
  if pageConfig.luaPath then
    cls = require(pageConfig.luaPath).new()
  elseif pageConfig.luaRelatePath then
    cls = require(self._uiConfig.prefix.luaPathPrefix .. pageConfig.luaRelatePath).new()
  else
    cls = require(self._uiConfig.prefix.luaPathPrefix .. pageName).new()
  end
  local page = {}
  page.pageName = pageName
  page.assetName = assetName
  page.cls = cls
  page.config = pageConfig
  page.status = "opening"
  page.options = options
  page.cls.pageName = pageName
  page.cls.status = "opening"
  local isRestore = options and options._restore
  local loadSync = isRestore or self._uiConfig.config.loadSync or pageConfig.forceSync
  local onLoadedFunc = function(o)
    self:profilerMarkTimeForRecord(page.pageName, "t_newPageEnd")
    self:_onPageObjLoaded(page, o, callback)
  end
  if isRestore then
    self:execMiddleware("loadAsyncHandler", assetName, page.cls, loadSync, onLoadedFunc, self._rootTrans)
  else
    self:checkPage(page, options, function(result)
      if result then
        self:execMiddleware("loadAsyncHandler", assetName, page.cls, loadSync, onLoadedFunc, self._rootTrans)
      else
        self:profilerMarkTimeForRecord(page.pageName, "t_newPageEnd")
        if callback ~= nil then
          callback(nil)
        end
      end
    end)
  end
  return page
end

function L_UI:checkPage(page, options, callback)
  local cls = page and page.cls
  if cls and cls.check then
    self:profilerMarkTimeForRecord(page.pageName, "t_checkStart")
    cls:check(options, function(result)
      self:profilerMarkTimeForRecord(page.pageName, "t_checkEnd")
      if callback ~= nil then
        callback(result == nil or result == true)
      end
    end)
    return
  elseif cls == nil then
    errorf("page.cls is nil")
  end
  if callback ~= nil then
    callback(true)
  end
end

function L_UI:initialize(config)
  self._uiConfig = config
  self._autoDisableRaycasterInTransition = config.config.enableRaycasterAsync
  self:_initUICamera(config.config)
  self:_initPages()
  self:_initAuxiliary()
  local goRoot = C_GameObject.Find("[UI Root]")
  CS.UnityEngine.Object.DontDestroyOnLoad(goRoot)
  self._rootTrans = goRoot.transform
  self._canvasScaler = goRoot:TryGetComponent(typeof(C_CanvasScaler))
  local tempTopMaskTrans = self._rootTrans:Find("[TempTopMask]")
  if tempTopMaskTrans then
    self._tempTopMask = tempTopMaskTrans.gameObject
    self._tempTopMask:SetActive(false)
    self._tmpTopGuideMask = CS.UnityEngine.Object.Instantiate(self._tempTopMask, self._rootTrans)
    self._tmpTopGuideMask.name = "[TempTopGuideMask]"
  else
    self._tempTopMask = nil
  end
  local playAudio = handler(self, self.playAudio)
  C_LButton.playAudioProvider = playAudio
  C_LToggle.playAudioProvider = playAudio
  C_LTabItem.playAudioProvider = playAudio
  C_LSlider.playAudioProvider = playAudio
  self._profiler = require("core.ui.profiler").new(self)
end

function L_UI:_internalOnOpened(pageName, pageConfig, result, callback)
  if pageConfig.uiType == self.groupKeys.stack and self._autoDisableRaycasterInTransition and pageConfig and not pageConfig.dontDisableRaycast then
    self:enableRaycaster()
  end
  if result then
    self:dispatch(self.pageEvent.opened, pageName)
  end
  if callback ~= nil then
    callback(result)
  end
end

function L_UI:_internalOpen(pageName, pageConfig, options, callback)
  self:dispatch(self.pageEvent.reqOpen, pageName)
  if pageConfig == nil then
    errorf("don't has this pageName: " .. pageName)
    return
  end
  if pageConfig.uiType == nil then
    errorf("don't has this uiType: " .. pageName)
    return
  end
  if pageConfig.uiType == self.groupKeys.stack and self._autoDisableRaycasterInTransition and pageConfig and not pageConfig.dontDisableRaycast then
    self:disableRaycaster()
  end
  self:profilerCreateRecord(pageName)
  self:profilerMarkTimeForRecord(pageName, "t_allStart")
  self._groupMode[pageConfig.uiType]:open(pageName, options, function(re)
    self:profilerMarkTimeForRecord(pageName, "t_allEnd")
    self:profilerCompleteRecord(pageName)
    self:_internalOnOpened(pageName, pageConfig, re, callback)
  end)
end

function L_UI:open(pageName, options, callback, ignoreQueueCheck)
  local pageConfig = self._uiConfig.pages[pageName]
  if pageConfig == nil then
    errorf("don't has this pageName: " .. pageName)
    return
  end
  if not ignoreQueueCheck and L_PriorityShowQueueManager:checkUINeedCheckQueue(pageName) then
    L_PriorityShowQueueManager:open(pageName, options, callback)
    return
  end
  self:_internalOpen(pageName, pageConfig, options, callback)
end

function L_UI:openRoot(pageName, options, callback)
  self:_back()
  self:close(nil, {backAll = true}, function()
    self:open(pageName, options, callback)
  end)
end

function L_UI:backToRoot(callback)
  self:_back()
  self:close(nil, {backRoot = true}, callback)
end

function L_UI:_back()
  local stackTypeGroup = self._groupMode[self.groupKeys.stack]
  for i, v in pairs(stackTypeGroup:getAllPages()) do
    local config = self._uiConfig.pages[v.pageName]
    if v.gameObject then
      self:playAudio(self.audioEvent.pageClose, config.windowCloseAudio)
    else
      v.status = "closed"
    end
  end
end

function L_UI:getRoot()
  return self._rootTrans
end

function L_UI:getCanvasScaler()
  return self._canvasScaler
end

function L_UI:getRefResolution()
  return self._canvasScaler.referenceResolution
end

function L_UI:setRefResolution(v2)
  self._canvasScaler.referenceResolution = v2
end

function L_UI:playAudio(triggerEvent, eventName, isLoop)
  if eventName == nil or eventName == "" then
    eventName = self._uiConfig.audioDefault[triggerEvent]
  end
  if eventName == nil or eventName == "" or eventName == "(none)" then
    return
  end
  if triggerEvent == self.audioEvent.pageBgm then
    self:execMiddleware("playBgm", eventName)
  else
    self:execMiddleware("play", eventName, nil)
  end
end

function L_UI:setInterfaceState(stateName, pageName)
  if string.isEmpty(stateName) then
    return
  end
  self:execMiddleware("setInterfaceState", stateName, pageName)
end

function L_UI:update()
  for i, v in pairs(self._groupMode) do
    if v.update then
      v:update()
    end
  end
  for i, v in pairs(self._auxiliaries) do
    local cls = v.cls
    if cls and cls.update then
      cls:update()
    end
  end
end

function L_UI:getUpdateHandler()
  if self._hdl_update == nil then
    function self._hdl_update()
      self:update()
    end
  end
  return self._hdl_update
end

function L_UI:currentPage(uiType)
  return self._groupMode[uiType]:currentPage()
end

function L_UI:stackRootPage()
  return self._groupMode[self.groupKeys.stack]:rootPage()
end

function L_UI:stackBottomVisiblePage()
  return self._groupMode[self.groupKeys.stack]:bottomVisiblePage()
end

function L_UI:middleware(func)
  self.middlewares = self.middlewares or {}
  table.insert(self.middlewares, func(self))
end

function L_UI:execMiddleware(funcName, ...)
  for i, v in ipairs(self.middlewares) do
    if v[funcName] then
      v[funcName](...)
    end
  end
end

function L_UI:enableRaycaster()
  for i, v in pairs(self._groupMode) do
    v:enableRaycaster()
  end
end

function L_UI:disableRaycaster()
  for i, v in pairs(self._groupMode) do
    v:disableRaycaster()
  end
end

function L_UI:setPageRaycasterEnabled(page, enabled)
  if page == nil then
    return
  end
  if self:isAutoDisableRaycasterInTransition() then
    local raycaster = page.graphicRaycaster
    if raycaster and not raycaster:IsNull() then
      raycaster.enabled = enabled
    end
  end
end

function L_UI:_internalOnClosed(pageName, restoreInput, callback, needForceRefresh)
  if needForceRefresh then
    if not restoreInput then
      self:disableRaycaster()
    end
    self:enableRaycaster()
  elseif restoreInput then
    self:enableRaycaster()
  end
  if pageName then
    self:playAudio(self.audioEvent.pageClose, self._uiConfig.pages[pageName].windowCloseAudio)
  end
  if callback ~= nil then
    callback(pageName)
  end
end

function L_UI:close(pageName, options, callback)
  local pageConfig = self._uiConfig.pages[pageName]
  local t = ""
  if pageConfig == nil then
    t = "stack"
  else
    t = pageConfig.uiType
  end
  local inputDisabled = false
  if t == self.groupKeys.stack and self._autoDisableRaycasterInTransition and pageConfig and not pageConfig.dontDisableRaycast then
    self:disableRaycaster()
    inputDisabled = true
  end
  self:dispatch(self.pageEvent.reqClose, pageName)
  self._groupMode[t]:close(pageName, options, function(closePageName)
    self:_internalOnClosed(closePageName, inputDisabled, callback, t == self.groupKeys.stack)
  end)
end

function L_UI:show(pageName, options)
  local pageConfig = self._uiConfig.pages[pageName]
  if pageConfig == nil then
    errorf("dont has this pageName:" .. pageName)
    return
  end
  self._groupMode[pageConfig.uiType]:show(pageName, options)
  self:dispatch(self.pageEvent.showed, pageName)
end

function L_UI:hide(pageName, options)
  local pageConfig = self._uiConfig.pages[pageName]
  if pageConfig == nil then
    errorf("dont has this pageName:" .. pageName)
    return
  end
  self._groupMode[pageConfig.uiType]:hide(pageName, options)
  self:dispatch(self.pageEvent.hided, pageName)
end

function L_UI:destroy(pageName, options)
  local pageConfig = self._uiConfig.pages[pageName]
  if pageConfig == nil then
    self._groupMode.stack:destroy(pageName, options)
  else
    self._groupMode[pageConfig.uiType]:destroy(pageName, options)
  end
end

function L_UI:getPage(pageName)
  for i, v in pairs(self._groupMode) do
    local page = v:getPage(pageName)
    if page ~= nil then
      return page
    end
  end
  return nil
end

function L_UI:checkPageOpen(pageName)
  for i, v in pairs(self._groupMode) do
    local page = v:getPage(pageName)
    if page ~= nil then
      return page.status == "shown"
    end
  end
  return false
end

function L_UI:getAllPages(group)
  local groupMgr = self._groupMode[group]
  if groupMgr then
    return groupMgr:getAllPages()
  else
    return nil
  end
end

function L_UI:dispatch(eventType, options)
  self._eventCenter:send(eventType, options)
end

function L_UI:addListener(eventType, callback, target, obj, priority)
  local event = self._eventCenter:register(eventType, callback, target, obj, priority)
  if obj then
    event.activeIn = true
  end
  return event
end

function L_UI:removeListener(eventType, callback, target)
  self._eventCenter:unRegister(eventType, callback, target)
end

function L_UI:info(group)
  if self._groupMode[group] ~= nil and self._groupMode[group].info ~= nil then
    return self._groupMode[group]:info()
  end
  return nil
end

function L_UI:pageInfo(group, pageName)
  if self._groupMode[group] ~= nil and self._groupMode[group].pageInfo ~= nil then
    return self._groupMode[group]:pageInfo(pageName)
  end
  return nil
end

function L_UI:getPageInfo(pageName)
  local pageConfig = self._uiConfig.pages[pageName]
  local group = pageConfig and pageConfig.uiType or self.groupKeys.stack
  return self:pageInfo(group, pageName)
end

function L_UI:getPageStatus(pageName)
  local page = self:getPage(pageName)
  return page and page.status
end

function L_UI:refreshAllPages()
  for i, v in pairs(self._groupMode) do
    v:refreshAllPages()
  end
end

function L_UI:clearAllPages()
  for i, v in pairs(self._groupMode) do
    v:clearAllPages()
  end
end

function L_UI:isAutoDisableRaycasterInTransition()
  return self._autoDisableRaycasterInTransition
end

function L_UI:setLoadSync(isSync)
  self._uiConfig.config.loadSync = isSync
end

function L_UI:setTempTopMaskVisible(visible)
  if self._tempTopMask then
    self._tempTopMask:SetActive(visible)
  end
end

function L_UI:setTempTopGuideMask(visible)
  if self._tmpTopGuideMask then
    self._tmpTopGuideMask:SetActive(visible)
  end
end

function L_UI:getProfiler()
  return self._profiler
end

function L_UI:profilerCreateRecord(pageName)
  if not self._profiler:isEnabled() then
    return nil
  end
  local record = self._profiler:createRecord(pageName)
  return record
end

function L_UI:profilerCompleteRecord(pageName)
  if not self._profiler:isEnabled() then
    return
  end
  local record = self._profiler:getRecordingRecord(pageName)
  if record then
    self._profiler:completeRecord(record)
  end
end

function L_UI:profilerMarkTimeForRecord(pageName, markerName)
  if not self._profiler:isEnabled() then
    return
  end
  local record = self._profiler:getRecordingRecord(pageName)
  if record then
    record:mark(markerName)
  end
end

function L_UI:getPageUiType(pageName)
  local pageConfig = self._uiConfig.pages[pageName]
  if pageConfig ~= nil then
    return pageConfig.uiType
  end
  return "stack"
end

function L_UI:getPageConfig(pageName)
  return self._uiConfig.pages[pageName]
end

function L_UI:processAudioInterfaceStatus(page)
  if page then
    local cfg = page.config
    local displayMode = cfg and cfg.displayMode
    local stateName = ""
    if displayMode == "sceneUI" or displayMode == "fullScreen" then
      stateName = "Interface_FullScreen"
    else
      stateName = "Interface_HalfScreen"
    end
    self:setInterfaceState(stateName, page.pageName)
  end
end

function L_UI:controlUIRootShowState(bShow)
  if not L_CommonUtil.isValid(self._rootTrans) then
    return
  end
  L_CommonUtil.setObjActive(self._rootTrans.gameObject, bShow)
end

return L_UI
