local core = require "core"
local common = require "core.common"
local style = require "core.style"
local FontCache = require "widget.fonts.cache"
local StatusView = require "core.statusview"

---@class widget.fonts
local Fonts = {}

---@type widget.fonts.cache | nil
local fontcache = nil

---@type table<integer, string> | nil
local fonts = nil

---Last time the status view item was rendered
local last_statusview_render = 0

---The amount of fonts matching the user query
local matching_fonts = 0

---Flag that indicates if command view font picker is for monospaced
local pick_monospaced = false

---Generate the list of fonts displayed on the CommandView.
---@param monospaced? boolean Only display fonts detected as monospaced.
local function generate_fonts(monospaced)
  if fontcache then
    if fontcache.building then monospaced = false end
    fonts = {}
    for idx, f in ipairs(fontcache.fonts) do
      if not monospaced or (monospaced and f.monospace) then
        table.insert(fonts, f.fullname .. "||" .. idx)
      end
    end
  end
end

---Helper function to split a string by a given delimeter.
local function split(s, delimeter, delimeter_pattern)
  if not delimeter_pattern then
    delimeter_pattern = delimeter
  end

  local result = {};
  for match in (s..delimeter):gmatch("(.-)"..delimeter_pattern) do
    table.insert(result, match);
  end
  return result;
end

local already_cleaning = false

---Clean the generated font cache used on command view to free some ram
local function clean_fonts_cache()
  if not fontcache or already_cleaning then return end
  if not fontcache.building and not fontcache.searching_monospaced then
    fontcache = nil
    fonts = nil
    collectgarbage "collect"
  else
    already_cleaning = true
    core.add_thread(function()
      while fontcache.building or fontcache.searching_monospaced do
        coroutine.yield(1)
      end
      if
        core.active_view ~= core.command_view
        or
        (
          core.command_view.label ~= "Select Font: "
          and
          core.command_view.label ~= "List only monospaced fonts?: "
        )
      then
        fontcache = nil
        fonts = nil
        collectgarbage "collect"
        already_cleaning = false
      end
    end)
  end
end

---Launch the commandview and let the user select a font.
---@param callback fun(name:string, path:string)
---@param monospaced boolean
function Fonts.show_picker(callback, monospaced)
  if not fontcache then fontcache = FontCache() end

  pick_monospaced = monospaced

  if not fontcache.building and (not monospaced or fontcache.monospaced) then
    generate_fonts(monospaced)
  else
    core.add_thread(function()
      while
        (fontcache.building or (monospaced and not fontcache.monospaced))
        and
        core.active_view == core.command_view
        and
        core.command_view.label == "Select Font: "
      do
        core.command_view:update_suggestions()
        coroutine.yield(2)
      end
      generate_fonts(monospaced)
      core.command_view:update_suggestions()
    end)
  end

  last_statusview_render = system.get_time()

  core.command_view:enter("Select Font", {
    submit = function(text, item)
      callback(item.text, item.info)
      clean_fonts_cache()
    end,
    suggest = function(text)
      if fontcache.building or (monospaced and fontcache.searching_monospaced) then
        generate_fonts(monospaced)
      end
      local res = common.fuzzy_match(fonts, text)
      matching_fonts = #res
      for i, name in ipairs(res) do
        local font_info = split(name, "||")
        local id = tonumber(font_info[2])
        local font_data = fontcache.fonts[id]
        res[i] = {
          text = font_data.fullname,
          info = font_data.path,
          id = id
        }
      end
      return res
    end,
    cancel = function()
      clean_fonts_cache()
    end
  })
end

---Same as `show_picker()` but asks the user if he wants a monospaced font.
---@param callback fun(name:string, path:string)
function Fonts.show_picker_ask_monospace(callback)
  if not fontcache then fontcache = FontCache() end

  core.command_view:enter("List only monospaced fonts?", {
    submit = function(text, item)
      Fonts.show_picker(callback, item.mono)
    end,
    suggest = function(text)
      local res = common.fuzzy_match({"Yes", "No"}, text)
      for i, name in ipairs(res) do
        res[i] = {
          text = name,
          mono = text == "Yes" and true or false
        }
      end
      return res
    end,
    cancel = function()
      clean_fonts_cache()
    end
  })
end

---Check if the font cache is been built.
---@return boolean building
function Fonts.cache_is_building()
  if not fontcache then return false end
  return fontcache:is_building()
end

---Remove current fonts cache file and regenerates a fresh one.
---@return boolean started False if cache is already been built
function Fonts.clean_cache()
  if not fontcache then fontcache = FontCache() end
  return fontcache:rebuild()
end

core.status_view:add_item({
  predicate = function()
    return core.active_view == core.command_view
      and core.command_view.label == "Select Font: "
  end,
  name = "widget:font-select",
  alignment = StatusView.Item.LEFT,
  get_item = function()
    local found = 0
    local dots, status = "", ""
    if fontcache then
      if fontcache.building or fontcache.searching_monospaced then
        dots = "."
        if system.get_time() - last_statusview_render >= 3 then
          last_statusview_render = system.get_time()
        elseif system.get_time() - last_statusview_render >= 2 then
          dots = "..."
        elseif system.get_time() - last_statusview_render >= 1 then
          dots = ".."
        end
      end
      if fontcache.building then
        status = " | searching system fonts" .. dots
      elseif fontcache.searching_monospaced then
        status = " | detecting monospaced fonts" .. dots
      end

      if fontcache.building or not pick_monospaced then
        found = fontcache.found
      else
        found = fontcache.found_monospaced
      end
    end

    return {
      style.text,
      style.font,
      "Matches: "
        .. tostring(matching_fonts)
        .. " / "
        .. tostring(found)
        .. status
    }
  end,
  position = 1
})


return Fonts
