local pluginName, pluginShared = ...
local Preview = {}
pluginShared.Preview = Preview

local guarded = pluginShared.guarded
local reported = pluginShared.reported or guarded
local L = LibStub("AceLocale-3.0"):GetLocale("AppearanceMatic")

function Preview:ResolveSource(visualID)
    if not (visualID and C_TransmogCollection and C_TransmogCollection.GetAppearanceSources) then
        return nil
    end
    local ok, sources = guarded(C_TransmogCollection.GetAppearanceSources, visualID)
    if not ok or type(sources) ~= "table" then
        return nil
    end
    local first = sources[1]
    if type(first) == "table" and first.sourceID then
        return first.sourceID
    end
    return nil
end

function Preview:PreviewVisual(visualID)
    if not self.panelShown or not self.model then
        return
    end
    local sourceID = self:ResolveSource(visualID)
    if not sourceID then
        return
    end
    guarded(self.model.SetUnit, self.model, "player")
    guarded(self.model.Dress, self.model)
    reported(self.model.TryOn, self.model, sourceID)
    self.tabardTicks = 12
    self:ApplyFacing()
end

function Preview:ActiveSlotName()
    local items = WardrobeCollectionFrame and WardrobeCollectionFrame.ItemsCollectionFrame
    if not items then
        return nil
    end
    local loc = items.transmogLocation
    if not loc and type(items.GetTransmogLocation) == "function" then
        loc = items:GetTransmogLocation()
    end
    if not (loc and type(loc.GetSlotName) == "function") then
        return nil
    end
    local ok, slotName = guarded(loc.GetSlotName, loc)
    if ok then
        return slotName
    end
    return nil
end

function Preview:CategoryFacing()
    local slotName = self:ActiveSlotName()
    if slotName == "MAINHANDSLOT" then
        return math.rad(80)
    elseif slotName == "SECONDARYHANDSLOT" then
        return -math.rad(80)
    end
    return 0
end

function Preview:ShowTabard()
    return AppearanceMaticDB and AppearanceMaticDB.preview and AppearanceMaticDB.preview.showTabard and true or false
end

function Preview:ApplyTabard()
    if not self.model or self:ShowTabard() then
        return
    end
    if self:ActiveSlotName() == "TABARDSLOT" then
        return
    end
    guarded(self.model.UndressSlot, self.model, INVSLOT_TABARD or 19)
end

function Preview:RefreshCurrent()
    local id = self.lastPreviewedVisualID
    if id then
        self.lastPreviewedVisualID = nil
        self:PreviewVisual(id)
        return
    end
    if self.model then
        guarded(self.model.SetUnit, self.model, "player")
        self.tabardTicks = 12
        self:ApplyFacing()
    end
end

function Preview:CurrentFacing()
    return self:CategoryFacing() + (self.rotationOffset or 0)
end

function Preview:ApplyFacing()
    if self.model then
        guarded(self.model.SetFacing, self.model, self:CurrentFacing())
    end
end

function Preview:Rotate(direction)
    local step = math.rad(20)
    self.rotationOffset = (self.rotationOffset or 0) + step * (direction or 1)
    self:ApplyFacing()
end

function Preview:HoveredVisualID()
    local foci
    if GetMouseFoci then
        foci = GetMouseFoci()
    elseif GetMouseFocus then
        local f = GetMouseFocus()
        foci = f and { f } or nil
    end
    if type(foci) ~= "table" then
        return nil
    end
    for i = 1, #foci do
        local frame = foci[i]
        local hops = 0
        while frame and hops < 5 do
            local info = frame.visualInfo
            if type(info) == "table" and info.visualID then
                return info.visualID
            end
            frame = type(frame.GetParent) == "function" and frame:GetParent() or nil
            hops = hops + 1
        end
    end
    return nil
end

function Preview:OnPanelUpdate(elapsed)
    self.pollTimer = (self.pollTimer or 0) + (elapsed or 0)
    if self.pollTimer < 0.1 then
        return
    end
    self.pollTimer = 0
    local items = WardrobeCollectionFrame and WardrobeCollectionFrame.ItemsCollectionFrame
    if not (items and type(items.IsShown) == "function" and items:IsShown()) then
        return
    end
    self.panelShown = true
    if (self.tabardTicks or 0) > 0 then
        self.tabardTicks = self.tabardTicks - 1
        if type(IsUnitModelReadyForUI) ~= "function" or IsUnitModelReadyForUI("player") then
            self:ApplyTabard()
        end
    end
    local visualID = self:HoveredVisualID()
    if visualID and visualID ~= self.lastPreviewedVisualID then
        self.lastPreviewedVisualID = visualID
        self:PreviewVisual(visualID)
    end
end

function Preview:EnsurePanel()
    if self.panel or type(CreateFrame) ~= "function" or type(CollectionsJournal) ~= "table" then
        return
    end
    local panel = CreateFrame("Frame", "AppearanceMaticPreviewPanel", CollectionsJournal, "BasicFrameTemplateWithInset")
    panel:SetPoint("TOPLEFT", CollectionsJournal, "TOPRIGHT", -3, 0)
    panel:SetPoint("BOTTOMLEFT", CollectionsJournal, "BOTTOMRIGHT", -3, 0)
    panel:SetWidth(400)
    panel:Hide()
    local model = CreateFrame("DressUpModel", nil, panel)
    model:SetPoint("TOPLEFT", 12, -28)
    model:SetPoint("BOTTOMRIGHT", -12, 44)
    guarded(model.SetUnit, model, "player")
    self.panel = panel
    self.model = model
    self.rotationOffset = self.rotationOffset or 0
    self.tabardTicks = 12
    self:ApplyFacing()
    local collapse = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    collapse:SetSize(20, 18)
    collapse:SetPoint("TOPRIGHT", -24, -1)
    collapse:SetText("-")
    collapse:SetScript("OnClick", function()
        Preview:ToggleCollapsed()
    end)
    self.collapseButton = collapse
    local tabardCheck = CreateFrame("CheckButton", nil, panel, "UICheckButtonTemplate")
    tabardCheck:SetSize(24, 24)
    tabardCheck:SetPoint("TOPRIGHT", panel, "TOPRIGHT", -48, -26)
    if tabardCheck.Text then
        tabardCheck.Text:SetText(L["prev_show_tabard"])
        tabardCheck.Text:ClearAllPoints()
        tabardCheck.Text:SetPoint("RIGHT", tabardCheck, "LEFT", -2, 0)
    end
    tabardCheck:SetChecked(self:ShowTabard())
    tabardCheck:SetScript("OnClick", function(owner)
        AppearanceMaticDB = AppearanceMaticDB or {}
        AppearanceMaticDB.preview = AppearanceMaticDB.preview or {}
        AppearanceMaticDB.preview.showTabard = owner:GetChecked() and true or false
        Preview:RefreshCurrent()
    end)
    self.tabardCheck = tabardCheck
    local rotateLeft = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    rotateLeft:SetSize(32, 22)
    rotateLeft:SetPoint("BOTTOMLEFT", 16, 12)
    rotateLeft:SetText("<")
    rotateLeft:SetScript("OnClick", function()
        Preview:Rotate(-1)
    end)
    self.rotateLeft = rotateLeft
    local rotateRight = CreateFrame("Button", nil, panel, "UIPanelButtonTemplate")
    rotateRight:SetSize(32, 22)
    rotateRight:SetPoint("BOTTOMRIGHT", -16, 12)
    rotateRight:SetText(">")
    rotateRight:SetScript("OnClick", function()
        Preview:Rotate(1)
    end)
    self.rotateRight = rotateRight
    panel:SetScript("OnUpdate", function(_, elapsed)
        Preview:OnPanelUpdate(elapsed)
    end)
    self:ApplyCollapsed(AppearanceMaticDB and AppearanceMaticDB.preview and AppearanceMaticDB.preview.collapsed)
end

function Preview:ApplyCollapsed(collapsed)
    if not self.panel then
        return
    end
    AppearanceMaticDB = AppearanceMaticDB or {}
    AppearanceMaticDB.preview = AppearanceMaticDB.preview or {}
    AppearanceMaticDB.preview.collapsed = collapsed and true or false
    if collapsed then
        if self.model then
            self.model:Hide()
        end
        if self.rotateLeft then
            self.rotateLeft:Hide()
        end
        if self.rotateRight then
            self.rotateRight:Hide()
        end
        if self.tabardCheck then
            self.tabardCheck:Hide()
        end
        self.panel:SetWidth(30)
        if self.collapseButton then
            self.collapseButton:SetText("+")
        end
    else
        if self.model then
            self.model:Show()
        end
        if self.rotateLeft then
            self.rotateLeft:Show()
        end
        if self.rotateRight then
            self.rotateRight:Show()
        end
        if self.tabardCheck then
            self.tabardCheck:Show()
        end
        self.panel:SetWidth(400)
        if self.collapseButton then
            self.collapseButton:SetText("-")
        end
    end
end

function Preview:ToggleCollapsed()
    local now = not (AppearanceMaticDB and AppearanceMaticDB.preview and AppearanceMaticDB.preview.collapsed)
    self:ApplyCollapsed(now)
end

function Preview:UpdateVisibility()
    if not self.panel then
        return
    end
    if self.panelShown then
        self.panel:Show()
    else
        self.panel:Hide()
    end
end

function Preview:InstallHooks()
    if self.hooked or type(hooksecurefunc) ~= "function" then
        return
    end
    local items = WardrobeCollectionFrame and WardrobeCollectionFrame.ItemsCollectionFrame
    if type(items) ~= "table" or type(items.UpdateItems) ~= "function" then
        return
    end
    self.hooked = true
    self:EnsurePanel()
    self.panelShown = (type(items.IsShown) == "function" and items:IsShown()) and true or false
    self:UpdateVisibility()
    if type(items.HookScript) == "function" then
        items:HookScript("OnShow", function()
            Preview.panelShown = true
            Preview:UpdateVisibility()
        end)
        items:HookScript("OnHide", function()
            Preview.panelShown = false
            Preview:UpdateVisibility()
        end)
    end
end

local previewLoader = CreateFrame and CreateFrame("Frame")
if previewLoader then
    previewLoader:RegisterEvent("ADDON_LOADED")
    previewLoader:SetScript("OnEvent", function(_, event, name)
        if event == "ADDON_LOADED" and (name == "Blizzard_Collections" or name == pluginName) then
            Preview:InstallHooks()
        end
    end)
    local isLoaded = C_AddOns and C_AddOns.IsAddOnLoaded or IsAddOnLoaded
    if type(isLoaded) == "function" then
        local ok, loaded = guarded(isLoaded, "Blizzard_Collections")
        if ok and loaded then
            Preview:InstallHooks()
        end
    end
end
