local BD = require("ui/bidi")
local Blitbuffer = require("ffi/blitbuffer")
local Device = require("device")
local Geom = require("ui/geometry")
local Math = require("optmath")
local ProgressWidget = require("ui/widget/progresswidget")
local ReaderFooter = require("apps/reader/modules/readerfooter")
local ReaderToc = require("apps/reader/modules/readertoc")
local T = require("ffi/util").template
local _ = require("gettext")

local Screen = Device.screen
local userpatch = require("userpatch")
local initial_marker_height_threshold = userpatch.getUpValue(ProgressWidget.paintTo, "INITIAL_MARKER_HEIGHT_THRESHOLD")

-- local logger = require("logger")

function ProgressWidget:paintTo(bb, x, y)
    local my_size = self:getSize()

    -- same bar height if thin_ticks, need extra space for the taller markers
    local dy = self.thin_ticks and (Screen:scaleBySize(1) / 2) or 0
    my_size.h = my_size.h + dy * 2

    if not self.dimen then
        self.dimen = Geom:new {
            x = x,
            y = y,
            w = my_size.w,
            h = my_size.h,
        }
    else
        self.dimen.x = x
        self.dimen.y = y
        self.dimen.w = my_size.w
        self.dimen.h = my_size.h
    end
    if self.dimen.w == 0 or self.dimen.h == 0 then return end

    local _mirroredUI = BD.mirroredUILayout()
    -- We'll draw every bar element in order, bottom to top.
    local fill_width = my_size.w - 2 * (self.margin_h + self.bordersize)
    local fill_y = y + self.margin_v + self.bordersize
    local fill_height = my_size.h - 2 * (self.margin_v + self.bordersize)

    if self.radius == 0 then
        -- If we don't have rounded borders, we can start with a simple border colored rectangle.
        bb:paintRect(x, y + dy, my_size.w, my_size.h - dy * 2, self.bordercolor)

        -- And a full background bar inside (i.e., on top) of that.
        bb:paintRect(
            x + self.margin_h + self.bordersize,
            fill_y + dy,
            math.ceil(fill_width),
            math.ceil(fill_height) - dy * 2,
            self.bgcolor
        )
    else
        -- Otherwise, we have to start with the background.
        bb:paintRoundedRect(x, y, my_size.w, my_size.h, self.bgcolor, self.radius)
        -- Then the border around that.
        bb:paintBorder(
            math.floor(x),
            math.floor(y),
            my_size.w,
            my_size.h,
            self.bordersize,
            self.bordercolor,
            self.radius
        )
    end

    -- Then we can just paint the fill rectangle(s) and tick(s) on top of that.
    -- First the fill bar(s)...
    -- Fill bar for alternate pages (e.g. non-linear flows).
    if self.alt and self.alt[1] ~= nil then
        for i = 1, #self.alt do
            local tick_x = fill_width * ((self.alt[i][1] - 1) / self.last)
            local width = fill_width * (self.alt[i][2] / self.last)
            if _mirroredUI then tick_x = fill_width - tick_x - width end
            tick_x = math.floor(tick_x)
            width = math.ceil(width)

            bb:paintRect(
                x + self.margin_h + self.bordersize + tick_x,
                fill_y + dy,
                width,
                math.ceil(fill_height) - dy * 2,
                self.altcolor
            )
        end
    end

    -- Main fill bar for the specified percentage.
    if self.percentage >= 0 and self.percentage <= 1 then
        local fill_x = x + self.margin_h + self.bordersize
        if self.fill_from_right or (_mirroredUI and not self.fill_from_right) then
            fill_x = fill_x + (fill_width * (1 - self.percentage))
            fill_x = math.floor(fill_x)
        end

        bb:paintRect(
            fill_x,
            fill_y + dy,
            math.ceil(fill_width * self.percentage),
            math.ceil(fill_height) - dy * 2,
            self.fillcolor
        )

        -- Overlay the initial position marker on top of that
        if self.initial_pos_marker and self.initial_percentage >= 0 then
            if self.height <= initial_marker_height_threshold then
                self.initial_pos_icon:paintTo(
                    bb,
                    Math.round(fill_x + math.ceil(fill_width * self.initial_percentage) - self.height / 4),
                    y - Math.round(self.height / 6)
                )
            else
                self.initial_pos_icon:paintTo(
                    bb,
                    Math.round(fill_x + math.ceil(fill_width * self.initial_percentage) - self.height / 2),
                    y
                )
            end
        end
    end

    -- ...then the tick(s).
    if self.ticks and self.last and self.last > 0 then
        local filled = math.floor(fill_width * self.percentage)
        local markers = {}
        if self.thin_ticks then
            markers.read = { width = self.tick_width, color = Blitbuffer.COLOR_WHITE }
            markers.unread = { width = self.tick_width * 0.5, color = self.bordercolor }
        else
            markers.read = { width = self.tick_width, color = self.bordercolor }
            markers.unread = markers.read
        end
        for i, tick in ipairs(self.ticks) do
            local tick_x = fill_width * (tick / self.last)
            if _mirroredUI then tick_x = fill_width - tick_x end
            tick_x = math.floor(tick_x)
            -- color depend on the tick placement
            local marker = tick_x < filled and markers.read or markers.unread
            bb:paintRect(
                x + self.margin_h + self.bordersize + tick_x,
                fill_y,
                marker.width,
                math.ceil(fill_height),
                marker.color
            )
        end
    end
end

local orig_ReaderFooter_setTocMarkers = ReaderFooter.setTocMarkers
local orig_ReaderToc_getTocTicks = ReaderToc.getTocTicks
local was_thin_ticks

ReaderFooter.setTocMarkers = function(self, reset)
    self.progress_bar.thin_ticks = self.settings.progress_style_thin and self.settings.toc_markers -- check ProgressWidget.paintTo

    local force_reset = false
    if self.progress_bar.thin_ticks ~= was_thin_ticks then
        self.ui.toc.ticks_flattened = nil
        force_reset = true
    end
    was_thin_ticks = self.progress_bar.thin_ticks

    if self.progress_bar.thin_ticks then -- force TOC to level 1 to avoid cluttering the status bar
        ReaderToc.getTocTicks = function(self, level) return { orig_ReaderToc_getTocTicks(self, 1) } end
    end

    local save_thin_setting = self.settings.progress_style_thin
    self.settings.progress_style_thin = false -- prevent premature exit

    orig_ReaderFooter_setTocMarkers(self, reset or force_reset)

    self.settings.progress_style_thin = save_thin_setting
    ReaderToc.getTocTicks = orig_ReaderToc_getTocTicks
    return true
end

local function patchMenuItem(attrib_name, replacement, menu, ...)
    local function findItem(sub_items, texts)
        local find = {}
        local texts = type(texts) == "table" and texts or { texts }
        -- stylua: ignore
        for _, text in ipairs(texts) do find[text] = true end
        for _, item in ipairs(sub_items) do
            local text = item.text or (item.text_func and item.text_func())
            if text and find[text] then return item end
        end
    end

    local function findItemFromPath(menu, path)
        local sub_items, item
        for _, text in ipairs(path) do
            sub_items = (item or menu).sub_item_table
            if not sub_items then return end
            item = findItem(sub_items, text)
            if not item then return end
        end
        return item
    end

    local item = findItemFromPath(menu, { ... })
    if item and item[attrib_name] then
        item[attrib_name] = replacement
        -- local path = { ... }
        -- for i, t in ipairs(path) do
        --     if type(t) == "table" then path[i] = table.concat(t, " | ") end
        -- end
        -- logger.info("Patch", attrib_name, "in '", table.concat(path, " > "), "'")
    end
end

local orig_ReaderFooter_addToMainMenu = ReaderFooter.addToMainMenu

ReaderFooter.addToMainMenu = function(self, menu_items)
    orig_ReaderFooter_addToMainMenu(self, menu_items)

    patchMenuItem(
        "callback",
        function()
            self.settings.progress_style_thin = true
            local bar_height = self.settings.progress_style_thin_height
            self.progress_bar:updateStyle(false, bar_height)
            self:setTocMarkers()
            self:refreshFooter(true, true)
        end,
        menu_items.status_bar,
        _("Progress bar"),
        { _("Thickness and height: thin"), _("Thickness and height: thick") },
        _("Thin")
    )

    patchMenuItem(
        "enabled_func",
        function() return not self.settings.chapter_progress_bar and not self.settings.disable_progress_bar end,
        menu_items.status_bar,
        _("Progress bar"),
        _("Show chapter markers")
    )

    patchMenuItem(
        "enabled_func",
        function()
            return not self.settings.chapter_progress_bar
                and self.settings.toc_markers
                and not self.settings.disable_progress_bar
        end,
        menu_items.status_bar,
        _("Progress bar"),
        T(_("Chapter marker width: %1"), self:genProgressBarChapterMarkerWidthMenuItems())
    )
end
