local addonTable = select(2, ...)
---@type MK
local MK = addonTable.MK
local core = addonTable
local TRANSLATE = core.TRANSLATE or function(msg) return msg end

core.MeetingStoneTab = core.MeetingStoneTab or {}
local MeetingStoneTab = core.MeetingStoneTab
local MeetingStoneBridge = core.MeetingStoneBridge or {}
local MeetingStoneListing = core.MeetingStoneListing or {}
local MeetingStoneForm = core.MeetingStoneForm or {}

local GetEditBoxText = MeetingStoneListing.GetEditBoxText
local SetMeetingStoneWidgetText = MeetingStoneListing.SetWidgetText
local SyncMeetingStoneNativeEntryCreationState = MeetingStoneListing.SyncNativeEntryCreationState
local CanManageMeetingStoneListing = MeetingStoneListing.CanManageListing
local HasMeetingStoneActiveListing = MeetingStoneListing.HasActiveListing
local IsMeetingStoneTextFilled = MeetingStoneListing.IsTextFilled
local RefreshMeetingStoneModernDropdown = MeetingStoneForm.RefreshDropdown

local function IsMeetingStoneLocalRemovalPending(owner)
    return MeetingStoneListing.IsLocalRemovalPending
        and MeetingStoneListing.IsLocalRemovalPending(owner) == true
end

local MEETINGSTONE_READ_ONLY_ROLE_ATLAS_BY_KEY = core.GROUP_LIST_ROLE_ATLAS_BY_KEY or {
    TANK = "UI-LFG-RoleIcon-Tank",
    HEAL = "UI-LFG-RoleIcon-Healer",
    DPS = "UI-LFG-RoleIcon-DPS",
}
local MEETINGSTONE_READ_ONLY_ROLE_KEY_BY_ASSIGNED_ROLE = {
    TANK = "TANK",
    HEALER = "HEAL",
    HEAL = "HEAL",
    DAMAGER = "DPS",
    DPS = "DPS",
}

local function RestoreMeetingStoneNativeTextFromDraft(widgets, draft, force)
    if not (widgets and draft and widgets.meetingStoneUsesNativeTextControls) then
        return
    end

    local title = draft.title or ""
    if title ~= ""
        and widgets.meetingStoneTitleBox
        and (force == true or GetEditBoxText(widgets.meetingStoneTitleBox) == "")
        and GetEditBoxText(widgets.meetingStoneTitleBox) ~= title
    then
        SetMeetingStoneWidgetText(widgets.meetingStoneTitleBox, title)
    end

    local summary = draft.summary or ""
    if summary ~= ""
        and widgets.meetingStoneSummaryBox
        and (force == true or GetEditBoxText(widgets.meetingStoneSummaryBox) == "")
        and GetEditBoxText(widgets.meetingStoneSummaryBox) ~= summary
    then
        SetMeetingStoneWidgetText(widgets.meetingStoneSummaryBox, summary)
    end
end

local function PrepareMeetingStoneNativeEditState(owner, widgets, draft, hasActiveEntry, nativeInputOccupied)
    if not (
        owner
        and hasActiveEntry
        and not nativeInputOccupied
        and widgets
        and widgets.meetingStoneUsesNativeTextControls
        and draft
        and draft.activityID
        and SyncMeetingStoneNativeEntryCreationState
    ) then
        if owner then
            owner.meetingStonePreparedNativeEditActivityID = nil
        end
        return
    end

    local activityID = tonumber(draft.activityID) or draft.activityID
    if owner.meetingStonePreparedNativeEditActivityID == activityID then
        return
    end

    owner.meetingStonePreparingNativeEditState = true
    SyncMeetingStoneNativeEntryCreationState(draft, { generalPlaystyle = draft.playStyle }, true, true, true)
    owner.meetingStonePreparingNativeEditState = nil
    owner.meetingStonePreparedNativeEditActivityID = activityID
end

local function ClearMeetingStoneReadOnlyDraft(owner)
    if not owner then
        return
    end

    owner.meetingStoneDraft = {
        title = "",
        summary = "",
        itemLevel = "0",
        score = "0",
    }
    owner.meetingStoneDraftID = (owner.meetingStoneDraftID or 0) + 1
end

local function GetMeetingStoneRequirementInputText(value, fallback)
    if value == nil then
        return fallback or "0"
    end
    return tostring(value)
end

local function ResetMeetingStoneUnavailableDraft(owner)
    if not owner then
        return
    end

    local draft = owner.meetingStoneDraft
    local needsReset = type(draft) ~= "table"
        or draft.activityID ~= nil
        or draft.mapID ~= nil
        or draft.activityName ~= nil
        or draft.dungeonName ~= nil
        or tostring(draft.title or "") ~= ""
        or tostring(draft.summary or "") ~= ""
        or tostring(draft.itemLevel or "0") ~= "0"
        or tostring(draft.score or "0") ~= "0"

    if needsReset then
        ClearMeetingStoneReadOnlyDraft(owner)
    end
end

local function UseMeetingStoneFallbackInputControls(widgets)
    if not widgets then
        return
    end

    if widgets.meetingStoneFallbackTitleBox then
        widgets.meetingStoneFallbackTitleBox:Show()
        widgets.meetingStoneTitleBox = widgets.meetingStoneFallbackTitleBox
    end
    if widgets.meetingStoneFallbackSummaryFrame then
        widgets.meetingStoneFallbackSummaryFrame:Show()
        widgets.meetingStoneSummaryFrame = widgets.meetingStoneFallbackSummaryFrame
        widgets.meetingStoneSummaryBox = widgets.meetingStoneFallbackSummaryBox
    end
    widgets.meetingStoneUsesNativeTextControls = false
end

local function ApplyMeetingStoneUnavailableState(owner, widgets)
    if not widgets then
        return
    end

    if owner and owner.DetachMeetingStoneNativeInputControls then
        owner:DetachMeetingStoneNativeInputControls()
    end

    UseMeetingStoneFallbackInputControls(widgets)
    ResetMeetingStoneUnavailableDraft(owner)
    local draft = owner and owner.meetingStoneDraft or {}

    SetMeetingStoneWidgetText(widgets.meetingStoneTitleBox, draft.title or "")
    SetMeetingStoneWidgetText(widgets.meetingStoneSummaryBox, draft.summary or "")
    SetMeetingStoneWidgetText(widgets.meetingStoneItemLevelBox, GetMeetingStoneRequirementInputText(draft.itemLevel, "0"))
    SetMeetingStoneWidgetText(widgets.meetingStoneScoreBox, GetMeetingStoneRequirementInputText(draft.score, "0"))
    widgets.meetingStoneDraftID = owner and owner.meetingStoneDraftID or widgets.meetingStoneDraftID

    if widgets.meetingStoneDungeonDropdown then
        RefreshMeetingStoneModernDropdown(widgets.meetingStoneDungeonDropdown)
    end
    if widgets.meetingStonePlayStyleDropdown then
        RefreshMeetingStoneModernDropdown(widgets.meetingStonePlayStyleDropdown)
    end
end

local function HasMeetingStoneActiveEntry()
    if HasMeetingStoneActiveListing then
        local ok, hasEntry = pcall(HasMeetingStoneActiveListing)
        return ok and hasEntry == true
    end
    if not (C_LFGList and C_LFGList.HasActiveEntryInfo) then
        return false
    end
    local ok, hasEntry = pcall(C_LFGList.HasActiveEntryInfo)
    return ok and hasEntry == true
end

local function SetMeetingStoneRegionShown(region, shown)
    if not region then
        return
    end
    if shown then
        region:Show()
    else
        region:Hide()
    end
end

local function SetMeetingStoneNativeOccupiedSpinnerShown(spinner, shown)
    if not spinner then
        return
    end

    if shown then
        spinner:Show()
        if spinner.Anim and spinner.Anim.Play and (not spinner.Anim.IsPlaying or not spinner.Anim:IsPlaying()) then
            spinner.Anim:Play()
        end
    else
        if spinner.Anim and spinner.Anim.Stop then
            spinner.Anim:Stop()
        end
        spinner:Hide()
    end
end

local function SetMeetingStoneDisabledPlaceholderOverlaysShown(widgets, shown)
    if not widgets then
        return
    end

    for _, region in ipairs(widgets.meetingStoneDisabledPlaceholderOverlays or {}) do
        SetMeetingStoneRegionShown(region, shown)
    end
end

local function SetMeetingStoneNativeInputOccupied(widgets, occupied, restoreForm)
    if not widgets then
        return
    end

    occupied = occupied == true
    widgets.meetingStoneNativeInputOccupied = occupied or nil
    SetMeetingStoneRegionShown(widgets.meetingStoneNativeOccupiedFrame, occupied)
    SetMeetingStoneNativeOccupiedSpinnerShown(widgets.meetingStoneNativeOccupiedSpinner, occupied)
    if occupied then
        for _, region in ipairs(widgets.meetingStoneFormWidgets or {}) do
            SetMeetingStoneRegionShown(region, false)
        end
        SetMeetingStoneDisabledPlaceholderOverlaysShown(widgets, false)
        SetMeetingStoneRegionShown(widgets.meetingStoneReadOnlyInfoFrame, false)
    elseif restoreForm == true then
        for _, region in ipairs(widgets.meetingStoneFormWidgets or {}) do
            SetMeetingStoneRegionShown(region, true)
        end
        SetMeetingStoneDisabledPlaceholderOverlaysShown(widgets, false)
        SetMeetingStoneRegionShown(widgets.meetingStoneReadOnlyInfoFrame, false)
    end
end
MeetingStoneTab.SetNativeInputOccupied = SetMeetingStoneNativeInputOccupied

local function RequestMeetingStoneListingSync(owner)
    if not (owner and owner.RequestGroupSync) then
        return
    end

    local now = GetTime and GetTime() or 0
    local previous = owner.mkMeetingStoneListingSyncRequestAt or 0
    if now > 0 and (now - previous) < 1.5 then
        return
    end

    owner.mkMeetingStoneListingSyncRequestAt = now
    owner:RequestGroupSync()
end

local function GetMeetingStoneReadOnlySharedDraft(owner, draft)
    if not (owner and owner.GetMeetingStoneSharedLeaderDraft) then
        return nil
    end

    local activityID = draft and draft.activityID
    local shared = owner:GetMeetingStoneSharedLeaderDraft(activityID)
    if type(shared) == "table" and shared.active ~= false and shared.activityID then
        return shared
    end
    return nil
end

local function HasRecentMeetingStoneSharedListingRemoval(owner)
    if not owner or not owner.meetingStoneSharedLeaderListingRemovedAt then
        return false
    end
    local now = GetTime and GetTime() or time and time() or 0
    if now <= 0 then
        return true
    end
    return (now - owner.meetingStoneSharedLeaderListingRemovedAt) < 8
end

local function ShouldShowMeetingStoneReadOnlyInfo(owner, draft, canManage, hasActiveEntry)
    if canManage or type(draft) ~= "table" then
        return false
    end

    if GetMeetingStoneReadOnlySharedDraft(owner, draft) then
        return true
    end
    if HasRecentMeetingStoneSharedListingRemoval(owner) then
        return false
    end

    return hasActiveEntry == true and draft.activityID ~= nil
end

local function GetMeetingStoneDisplayText(value, fallback)
    if IsMeetingStoneTextFilled and IsMeetingStoneTextFilled(value) then
        return tostring(value)
    end
    return fallback or TRANSLATE("同步中")
end

local function AddMeetingStoneRoleKey(roleKeys, roleKey)
    if not roleKey or #roleKeys >= 5 then
        return
    end
    roleKeys[#roleKeys + 1] = roleKey
end

local function AddMeetingStoneUnitAssignedRole(roleKeys, unit, seenGuids)
    if not (UnitExists and UnitExists(unit)) then
        return
    end

    if UnitGUID then
        local guid = UnitGUID(unit)
        if guid then
            if seenGuids[guid] then
                return
            end
            seenGuids[guid] = true
        end
    end

    local assignedRole = UnitGroupRolesAssigned and UnitGroupRolesAssigned(unit)
    AddMeetingStoneRoleKey(roleKeys, MEETINGSTONE_READ_ONLY_ROLE_KEY_BY_ASSIGNED_ROLE[assignedRole])
end

local function AddMeetingStoneCharacterEntryRole(owner, roleKeys, characterEntry, activeAliases)
    if not (owner and owner.NormalizeRoles and characterEntry and characterEntry.data) then
        return
    end
    if owner.IsCharacterEntryCurrentlyInGroup and not owner:IsCharacterEntryCurrentlyInGroup(characterEntry, activeAliases) then
        return
    end

    local roles = owner:NormalizeRoles(characterEntry.data)
    if roles.TANK then
        AddMeetingStoneRoleKey(roleKeys, "TANK")
    elseif roles.HEAL then
        AddMeetingStoneRoleKey(roleKeys, "HEAL")
    elseif roles.DPS then
        AddMeetingStoneRoleKey(roleKeys, "DPS")
    end
end

local function GetMeetingStoneReadOnlyGroupRoleKeys(owner)
    local roleKeys = {}
    local seenGuids = {}

    AddMeetingStoneUnitAssignedRole(roleKeys, "player", seenGuids)
    if IsInRaid and IsInRaid() then
        local count = GetNumGroupMembers and GetNumGroupMembers() or 0
        for index = 1, count do
            AddMeetingStoneUnitAssignedRole(roleKeys, "raid" .. index, seenGuids)
            if #roleKeys >= 5 then
                return roleKeys
            end
        end
    elseif IsInGroup and IsInGroup() then
        for index = 1, 4 do
            AddMeetingStoneUnitAssignedRole(roleKeys, "party" .. index, seenGuids)
            if #roleKeys >= 5 then
                return roleKeys
            end
        end
    end

    if #roleKeys == 0 and owner and owner.GetSharedGroupViewCharacters then
        local activeAliases = owner.GetActiveGroupCharacterAliases and owner:GetActiveGroupCharacterAliases() or nil
        for _, characterEntry in ipairs(owner:GetSharedGroupViewCharacters()) do
            AddMeetingStoneCharacterEntryRole(owner, roleKeys, characterEntry, activeAliases)
            if #roleKeys >= 5 then
                break
            end
        end
    end

    return roleKeys
end

local function RefreshMeetingStoneReadOnlyRoleSlots(owner, widgets)
    local roleKeys = GetMeetingStoneReadOnlyGroupRoleKeys(owner)
    for index, slot in ipairs(widgets.meetingStoneReadOnlyRoleSlots or {}) do
        local roleKey = roleKeys[index]
        local atlasName = MEETINGSTONE_READ_ONLY_ROLE_ATLAS_BY_KEY[roleKey]
        if atlasName and slot.icon then
            if slot.icon.SetAtlas then
                slot.icon:SetAtlas(atlasName, false)
            else
                slot.icon:SetTexture("Interface\\LFGFrame\\UI-LFG-ICON-ROLES")
            end
            slot.icon:SetVertexColor(1, 1, 1, 0.95)
            slot.icon:Show()
            if slot.empty then
                slot.empty:Hide()
            end
        else
            if slot.icon then
                slot.icon:Hide()
            end
            if slot.empty then
                slot.empty:Show()
            end
        end
    end
end

local function RefreshMeetingStoneReadOnlyInfo(owner, widgets, draft)
    if not widgets then
        return
    end

    draft = draft or {}
    local dungeonName = GetMeetingStoneDisplayText(draft.activityName or draft.dungeonName)
    local title = GetMeetingStoneDisplayText(draft.title)
    local itemLevel = GetMeetingStoneDisplayText(draft.itemLevel, "0")
    local score = GetMeetingStoneDisplayText(draft.score, "0")

    if widgets.meetingStoneReadOnlyDungeonText then
        widgets.meetingStoneReadOnlyDungeonText:SetText(dungeonName)
    end
    if widgets.meetingStoneReadOnlyTitleText then
        widgets.meetingStoneReadOnlyTitleText:SetText(title)
    end
    if widgets.meetingStoneReadOnlyItemLevelText then
        widgets.meetingStoneReadOnlyItemLevelText:SetText(itemLevel)
    end
    if widgets.meetingStoneReadOnlyScoreText then
        widgets.meetingStoneReadOnlyScoreText:SetText(score)
    end

    RefreshMeetingStoneReadOnlyRoleSlots(owner, widgets)
end

local function ApplyMeetingStoneLeftPanelMode(owner, widgets, draft, canManage, hasActiveEntry)
    local showNativeOccupied = canManage == true and widgets.meetingStoneNativeInputOccupied == true
    local showReadOnlyInfo = ShouldShowMeetingStoneReadOnlyInfo(owner, draft, canManage, hasActiveEntry)
    if showNativeOccupied then
        showReadOnlyInfo = false
    end

    for _, region in ipairs(widgets.meetingStoneFormWidgets or {}) do
        SetMeetingStoneRegionShown(region, not showReadOnlyInfo and not showNativeOccupied)
    end
    if canManage == true or showReadOnlyInfo or showNativeOccupied then
        SetMeetingStoneDisabledPlaceholderOverlaysShown(widgets, false)
    end
    SetMeetingStoneRegionShown(widgets.meetingStoneReadOnlyInfoFrame, showReadOnlyInfo)
    SetMeetingStoneNativeInputOccupied(widgets, showNativeOccupied)

    if widgets.meetingStoneLeftHeaderTitle then
        widgets.meetingStoneLeftHeaderTitle:SetText(TRANSLATE("组队管理"))
    end

    if showReadOnlyInfo then
        RefreshMeetingStoneReadOnlyInfo(owner, widgets, draft)
    end
end

local function GetMeetingStoneApplicantCount()
    if not (C_LFGList and C_LFGList.GetApplicants) then
        return 0
    end

    local ok, applicants = pcall(C_LFGList.GetApplicants)
    return ok and type(applicants) == "table" and #applicants or 0
end

function MK:BuildMeetingStoneTabSnapshot()
    local managementAvailable = not self.IsMeetingStoneManagementAvailable or self:IsMeetingStoneManagementAvailable()
    local installed = managementAvailable and "1" or "0"
    local available = managementAvailable and "1" or "0"
    local loaded = managementAvailable and "1" or "0"
    local hasEntry = managementAvailable and HasMeetingStoneActiveEntry() and "1" or "0"
    local canManageListing = managementAvailable and (not CanManageMeetingStoneListing or CanManageMeetingStoneListing()) or false
    local canManage = canManageListing and "1" or "0"
    local draft = self.meetingStoneDraft or {}
    local applicantCount = managementAvailable and GetMeetingStoneApplicantCount() or 0
    return table.concat({
        "meetingstone",
        installed,
        available,
        loaded,
        hasEntry,
        canManage,
        tostring(draft.activityID or ""),
        tostring(draft.mapID or ""),
        tostring(draft.keyLevel or ""),
        tostring(draft.title or ""),
        tostring(draft.summary or ""),
        tostring(draft.itemLevel or ""),
        tostring(draft.score or ""),
        tostring(draft.playStyle or ""),
        tostring(applicantCount),
        self.widgets and self.widgets.meetingStoneNativeInputOccupied and "1" or "0",
    }, "|")
end

function MK:RefreshMeetingStoneTab(widgets, refreshSource)
    if not widgets then
        return
    end

    local meetingStoneAvailable = not self.IsMeetingStoneManagementAvailable or self:IsMeetingStoneManagementAvailable()
    if not meetingStoneAvailable then
        ApplyMeetingStoneUnavailableState(self, widgets)
        SetMeetingStoneNativeInputOccupied(widgets, false)
        ApplyMeetingStoneLeftPanelMode(self, widgets, self.meetingStoneDraft, true, false)
        self:RefreshMeetingStoneActionButtons(widgets)
        if core.MeetingStoneApplicantList and core.MeetingStoneApplicantList.Refresh then
            core.MeetingStoneApplicantList.Refresh(self, widgets, refreshSource)
        end
        return
    end

    local canManage = not CanManageMeetingStoneListing or CanManageMeetingStoneListing()
    local hasActiveEntry = HasMeetingStoneActiveEntry()
    local localRemovalPending = IsMeetingStoneLocalRemovalPending(self)
    if localRemovalPending then
        hasActiveEntry = false
    end
    local preserveManagedFormText = canManage
        and hasActiveEntry
        and self.ShouldPreserveMeetingStoneManagedFormText
        and self:ShouldPreserveMeetingStoneManagedFormText()
    if canManage then
        self:AttachMeetingStoneNativeInputControls(widgets)
    else
        if self.DetachMeetingStoneNativeInputControls then
            self:DetachMeetingStoneNativeInputControls()
        end
        UseMeetingStoneFallbackInputControls(widgets)
    end
    if not canManage then
        local restored = false
        local restoredFromNative = false
        if self.RestoreMeetingStoneReadOnlyDraftFromActiveEntry then
            restoredFromNative = self:RestoreMeetingStoneReadOnlyDraftFromActiveEntry(true) == true
            restored = restoredFromNative
        elseif self.RestoreMeetingStoneDraftFromActiveEntry then
            restoredFromNative = self:RestoreMeetingStoneDraftFromActiveEntry(true) == true
            restored = restoredFromNative
        end
        hasActiveEntry = hasActiveEntry or restoredFromNative
        local recentlyRemoved = not restoredFromNative and HasRecentMeetingStoneSharedListingRemoval(self)
        if not restored and not recentlyRemoved and self.RestoreMeetingStoneDraftFromSharedLeaderEntry then
            restored = self:RestoreMeetingStoneDraftFromSharedLeaderEntry()
        end
        if not restored then
            ClearMeetingStoneReadOnlyDraft(self)
        end
        local activeDraft = self.meetingStoneDraft or {}
        local sharedDraft = GetMeetingStoneReadOnlySharedDraft(self, activeDraft)
        local missingSharedText = not sharedDraft
            or not IsMeetingStoneTextFilled
            or not IsMeetingStoneTextFilled(sharedDraft.title)
        if missingSharedText and not restoredFromNative and not recentlyRemoved then
            RequestMeetingStoneListingSync(self)
        end
    elseif not localRemovalPending then
        if not preserveManagedFormText then
            self:RestoreMeetingStoneDraftFromActiveEntry(hasActiveEntry == true)
        end
    end

    local draft = self.meetingStoneDraft
    if widgets.meetingStoneDungeonDropdown then
        RefreshMeetingStoneModernDropdown(widgets.meetingStoneDungeonDropdown)
    end
    if widgets.meetingStonePlayStyleDropdown then
        RefreshMeetingStoneModernDropdown(widgets.meetingStonePlayStyleDropdown)
    end

    local nativeInputOccupied = widgets.meetingStoneNativeInputOccupied == true
    if draft then
        if widgets.meetingStoneDraftID ~= self.meetingStoneDraftID then
            if not nativeInputOccupied and draft.activityID and widgets.meetingStoneUsesNativeTextControls and not preserveManagedFormText then
                local preserveNativeDraftText = hasActiveEntry == true
                    or IsMeetingStoneTextFilled(draft.title)
                    or IsMeetingStoneTextFilled(draft.summary)
                SyncMeetingStoneNativeEntryCreationState(draft, { generalPlaystyle = draft.playStyle }, preserveNativeDraftText, hasActiveEntry == true)
            end
            if not nativeInputOccupied and not widgets.meetingStoneUsesNativeTextControls then
                SetMeetingStoneWidgetText(widgets.meetingStoneTitleBox, draft.title or "")
                SetMeetingStoneWidgetText(widgets.meetingStoneSummaryBox, draft.summary or "")
            end
            SetMeetingStoneWidgetText(widgets.meetingStoneItemLevelBox, GetMeetingStoneRequirementInputText(draft.itemLevel, "0"))
            SetMeetingStoneWidgetText(widgets.meetingStoneScoreBox, GetMeetingStoneRequirementInputText(draft.score, "0"))
            if widgets.meetingStonePrivateToggle then widgets.meetingStonePrivateToggle:SetChecked(draft.privateGroup == true) end
            widgets.meetingStoneDraftID = self.meetingStoneDraftID
        elseif not nativeInputOccupied and not widgets.meetingStoneUsesNativeTextControls and widgets.meetingStoneTitleBox and GetEditBoxText(widgets.meetingStoneTitleBox) == "" then
            SetMeetingStoneWidgetText(widgets.meetingStoneTitleBox, draft.title or "")
        end
        if not nativeInputOccupied then
            RestoreMeetingStoneNativeTextFromDraft(widgets, draft, preserveManagedFormText)
            PrepareMeetingStoneNativeEditState(self, widgets, draft, hasActiveEntry, nativeInputOccupied)
        end
    end

    ApplyMeetingStoneLeftPanelMode(self, widgets, draft, canManage, hasActiveEntry)
    self:RefreshMeetingStoneActionButtons(widgets)

    if core.MeetingStoneApplicantList and core.MeetingStoneApplicantList.Refresh then
        core.MeetingStoneApplicantList.Refresh(self, widgets, refreshSource)
    end
end

function MeetingStoneTab.Build(owner, tabGroup)
    if core.MeetingStoneLayout and core.MeetingStoneLayout.Build then
        return core.MeetingStoneLayout.Build(owner, tabGroup)
    end
end
