local MAJOR, MINOR = "LibMatic-IconPicker-1.0", 2
assert(LibStub, MAJOR .. " requires LibStub")
local lib = LibStub:NewLibrary(MAJOR, MINOR)
if not lib then return end

lib.frame = lib.frame or nil
lib.callback = lib.callback or nil
lib.selected = lib.selected or nil
lib.icons = lib.icons or {}
lib.matched = lib.matched or {}
lib.source = lib.source or "all"
lib.query = lib.query or ""
lib.searchTimer = lib.searchTimer or nil
lib.cells = lib.cells or {}
lib.preview = lib.preview or nil
lib.scroll = lib.scroll or nil
lib.canvas = lib.canvas or nil
lib.search = lib.search or nil

local FRAME_NAME = "LibMaticIconPickerFrame"
local W, H = 460, 420
local COLUMNS = 10
local CELL = 32
local PAD = 6
local STEP = CELL + PAD

local function harvest(mode)
    local out = {}
    local providerOK = false
    if _G.IconDataProviderMixin and _G.CreateAndInitFromMixin then
        local types
        if mode == "spells" and _G.IconDataProviderIconType then
            types = { _G.IconDataProviderIconType.Spell }
        elseif mode == "items" and _G.IconDataProviderIconType then
            types = { _G.IconDataProviderIconType.Item }
        end
        local extra = (_G.IconDataProviderExtraType and _G.IconDataProviderExtraType.None) or 0
        local ok, provider = pcall(_G.CreateAndInitFromMixin, _G.IconDataProviderMixin, extra, false, types)
        if ok and provider and provider.GetNumIcons then
            local n = provider:GetNumIcons() or 0
            for i = 1, n do out[#out + 1] = provider:GetIconByIndex(i) end
            providerOK = #out > 0
        end
    end
    if providerOK then return out end
    if mode ~= "items" and type(_G.GetMacroIcons) == "function" then
        local buf = {}
        _G.GetMacroIcons(buf)
        for _, v in ipairs(buf) do out[#out + 1] = v end
    end
    if mode ~= "spells" and type(_G.GetMacroItemIcons) == "function" then
        local buf = {}
        _G.GetMacroItemIcons(buf)
        for _, v in ipairs(buf) do out[#out + 1] = v end
    end
    return out
end

local function narrow()
    if lib.query == "" then
        lib.matched = lib.icons
        return
    end
    local needle = lib.query:lower()
    local hits = {}
    for i = 1, #lib.icons do
        local entry = lib.icons[i]
        if tostring(entry):lower():find(needle, 1, true) then
            hits[#hits + 1] = entry
        end
    end
    lib.matched = hits
end

local function paint()
    if not lib.scroll or not lib.canvas then return end
    local total = #lib.matched
    local rowCount = math.ceil(total / COLUMNS)
    lib.canvas:SetHeight(math.max(1, rowCount * STEP))
    local scrollY = lib.scroll:GetVerticalScroll() or 0
    local topRow = math.floor(scrollY / STEP)
    local visibleRows = math.ceil(lib.scroll:GetHeight() / STEP) + 1
    for r = 1, visibleRows do
        for c = 1, COLUMNS do
            local slotIndex = (r - 1) * COLUMNS + c
            local cell = lib.cells[slotIndex]
            if not cell then
                cell = CreateFrame("Button", nil, lib.canvas)
                cell:SetSize(CELL, CELL)
                cell.tex = cell:CreateTexture(nil, "ARTWORK")
                cell.tex:SetAllPoints()
                cell:SetHighlightTexture("Interface\\Buttons\\ButtonHilight-Square", "ADD")
                cell:SetScript("OnClick", function(self)
                    if self.fileID and lib.preview then
                        lib.preview.tex:SetTexture(self.fileID)
                        lib.selected = self.fileID
                    end
                end)
                lib.cells[slotIndex] = cell
            end
            local actualRow = r + topRow
            local idx = (actualRow - 1) * COLUMNS + c
            if idx <= total then
                local fid = lib.matched[idx]
                cell:ClearAllPoints()
                cell:SetPoint("TOPLEFT", lib.canvas, "TOPLEFT",
                    (c - 1) * STEP, -((actualRow - 1) * STEP))
                cell.tex:SetTexture(fid)
                cell.fileID = fid
                cell:Show()
            else
                cell:Hide()
            end
        end
    end
end

local function buildFrame()
    if lib.frame then return end
    local f = CreateFrame("Frame", FRAME_NAME, UIParent, "BackdropTemplate")
    f:SetSize(W, H)
    f:SetPoint("CENTER")
    f:SetMovable(true)
    f:EnableMouse(true)
    f:RegisterForDrag("LeftButton")
    f:SetScript("OnDragStart", f.StartMoving)
    f:SetScript("OnDragStop", f.StopMovingOrSizing)
    f:SetFrameStrata("FULLSCREEN_DIALOG")
    f:SetToplevel(true)
    f:SetClampedToScreen(true)
    if f.SetBackdrop then
        f:SetBackdrop({
            bgFile = "Interface\\DialogFrame\\UI-DialogBox-Background-Dark",
            edgeFile = "Interface\\DialogFrame\\UI-DialogBox-Border",
            tile = true, tileSize = 32, edgeSize = 32,
            insets = { left = 8, right = 8, top = 8, bottom = 8 },
        })
        f:SetBackdropColor(0.05, 0.05, 0.05, 0.95)
    end
    tinsert(UISpecialFrames, FRAME_NAME)

    local title = f:CreateFontString(nil, "OVERLAY", "GameFontNormalLarge")
    title:SetPoint("TOP", 0, -10)
    title:SetText("Choose Icon")

    local close = CreateFrame("Button", nil, f, "UIPanelCloseButton")
    close:SetPoint("TOPRIGHT", -4, -4)

    local preview = CreateFrame("Button", nil, f)
    preview:SetSize(48, 48)
    preview:SetPoint("TOPLEFT", 14, -36)
    preview.tex = preview:CreateTexture(nil, "ARTWORK")
    preview.tex:SetAllPoints()
    lib.preview = preview

    local search = CreateFrame("EditBox", nil, f, "InputBoxTemplate")
    search:SetSize(180, 22)
    search:SetPoint("LEFT", preview, "RIGHT", 16, 0)
    search:SetAutoFocus(false)
    search:SetScript("OnTextChanged", function(self)
        lib.query = self:GetText() or ""
        if lib.searchTimer then lib.searchTimer:Cancel() end
        lib.searchTimer = C_Timer.NewTimer(0.15, function()
            narrow()
            paint()
        end)
    end)
    lib.search = search

    local dd = CreateFrame("Frame", FRAME_NAME .. "Filter", f, "UIDropDownMenuTemplate")
    dd:SetPoint("LEFT", search, "RIGHT", 8, -2)
    if UIDropDownMenu_SetWidth then UIDropDownMenu_SetWidth(dd, 90) end
    if UIDropDownMenu_Initialize then
        UIDropDownMenu_Initialize(dd, function()
            local entries = {
                { id = "all",    text = "All Icons" },
                { id = "items",  text = "Items" },
                { id = "spells", text = "Spells" },
            }
            for _, e in ipairs(entries) do
                local info = UIDropDownMenu_CreateInfo()
                info.text = e.text
                info.value = e.id
                info.checked = (lib.source == e.id)
                info.func = function()
                    lib.source = e.id
                    if UIDropDownMenu_SetText then UIDropDownMenu_SetText(dd, e.text) end
                    lib.icons = harvest(lib.source)
                    narrow()
                    paint()
                end
                UIDropDownMenu_AddButton(info)
            end
        end)
        if UIDropDownMenu_SetText then UIDropDownMenu_SetText(dd, "All Icons") end
    end

    local scroll = CreateFrame("ScrollFrame", FRAME_NAME .. "Scroll", f, "UIPanelScrollFrameTemplate")
    scroll:SetPoint("TOPLEFT", 14, -92)
    scroll:SetPoint("BOTTOMRIGHT", -34, 40)
    local canvas = CreateFrame("Frame", nil, scroll)
    canvas:SetSize(COLUMNS * STEP, 1)
    scroll:SetScrollChild(canvas)
    scroll:SetScript("OnVerticalScroll", function(_, _) paint() end)
    lib.scroll = scroll
    lib.canvas = canvas

    local ok = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    ok:SetSize(90, 22)
    ok:SetPoint("BOTTOMRIGHT", -14, 12)
    ok:SetText(_G.OKAY or "OK")
    ok:SetScript("OnClick", function()
        local cb = lib.callback
        lib.callback = nil
        f:Hide()
        if cb then cb(lib.selected) end
    end)

    local cancel = CreateFrame("Button", nil, f, "UIPanelButtonTemplate")
    cancel:SetSize(90, 22)
    cancel:SetPoint("RIGHT", ok, "LEFT", -6, 0)
    cancel:SetText(_G.CANCEL or "Cancel")
    cancel:SetScript("OnClick", function()
        local cb = lib.callback
        lib.callback = nil
        f:Hide()
        if cb then cb(nil) end
    end)

    f:Hide()
    lib.frame = f
end

function lib:Open(currentIcon, callback)
    if InCombatLockdown and InCombatLockdown() then return end
    if type(callback) ~= "function" then return end
    buildFrame()
    self.callback = callback
    self.selected = currentIcon
    if self.preview then
        self.preview.tex:SetTexture(currentIcon or "interface\\icons\\inv_misc_questionmark")
    end
    self.query = ""
    if self.search then self.search:SetText("") end
    self.icons = harvest(self.source)
    narrow()
    paint()
    self.frame:Show()
end

function lib:Close()
    self.callback = nil
    if self.frame then self.frame:Hide() end
end

function lib:IsOpen()
    return (self.frame and self.frame:IsShown()) and true or false
end
