-- Grab environment
local string = string
local table = table

local wibox = require("wibox")
local awful = require("awful")
local beautiful = require("beautiful")

local flex = require("flex")
local modutil = require("flex.util")
local modtip = require("flex.float.hotkeys")

-- Initialize tables and vars for module
local keychain = {}
local tip_cache = {}

local label_pattern = { Mod1 = "A", Mod4 = "M", Control = "C", Shift = "S" }

-- key bindings
keychain.service = { close = { "Escape" }, help = { "F1" }, stepback = { "BackSpace" } }

-- Generate default theme vars
local function default_style()
	local style = {
		geometry = { width = 220, height = 60 },
		font = "Fira Code 14 bold",
		border_width = 2,
		keytip = { geometry = { width = 500 }, exit = false },
		color = { border = "#575757", wibox = "#202020" },
		shape = nil,
	}

	return flex.util.table.merge(style, flex.util.table.check(beautiful, "float.keychain") or {})
end

-- Support functions
local function build_label(item)
	if #item[1] == 0 then
		return item[2]
	end

	local mods = {}
	for _, m in ipairs(item[1]) do
		mods[#mods + 1] = label_pattern[m]
	end
	return string.format("%s-%s", table.concat(mods, "-"), item[2])
end

local function build_tip(store, item, prefix)
	prefix = prefix or build_label(item)
	for _, k in ipairs(item[3]) do
		local p = prefix .. " " .. build_label(k)
		if type(k[3]) == "table" then
			build_tip(store, k, p)
		else
			table.insert(store, { {}, p, nil, k[#k] })
		end
	end

	return store
end

local function build_fake_keys(keys)
	local res = {}
	for _, keygroup in ipairs({
		{ description = "Undo last key", group = "Action", keyname = "stepback" },
		{ description = "Undo sequence", group = "Action", keyname = "close" },
		{ description = "Show hotkeys helper", group = "Action", keyname = "help" },
	}) do
		for _, k in ipairs(keys[keygroup.keyname]) do
			table.insert(res, {
				{},
				k,
				nil,
				{ description = keygroup.description, group = keygroup.group },
			})
		end
	end
	return res
end

-- Main widget
-- Initialize keychain widget
function keychain:init(style)
	-- Init vars
	self.active = nil
	self.parents = {}
	self.sequence = ""

	style = flex.util.table.merge(default_style(), style or {})
	self.style = style

	-- Wibox
	self.wibox = wibox({
		ontop = true,
		bg = style.color.wibox,
		border_width = style.border_width,
		border_color = style.color.border,
		shape = style.shape,
	})
	self.wibox:geometry(style.geometry)

	self.label = wibox.widget.textbox()
	self.label:set_align("center")
	self.wibox:set_widget(self.label)

	self.label:set_font(style.font)

	-- Keygrabber
	self.keygrabber = function(mod, key, event)
		if event == "press" then
			return false
		end

		-- dirty fix for first key release
		if self.actkey == key then
			self.actkey = nil
			return
		end

		if awful.util.table.hasitem(self.service.close, key) then
			self:hide()
		elseif awful.util.table.hasitem(self.service.stepback, key) then
			self:undo()
		elseif awful.util.table.hasitem(self.service.help, key) then
			modtip:show()
		else
			for _, item in ipairs(self.active[3]) do
				if modutil.key.match_grabber(item, mod, key) then
					self:activate(item)
					return
				end
			end
		end
	end
end

-- Set current key item
function keychain:activate(item, keytip)
	if not self.wibox then
		self:init()
	end
	self.actkey = keytip and item[2]

	if type(item[3]) == "function" then
		item[3]()
		self:hide()
	elseif type(item[3]) == "table" then
		if not self.active then
			modutil.placement.centered(self.wibox, nil, mouse.screen.workarea)
			self.wibox.visible = true
			awful.keygrabber.run(self.keygrabber)
		else
			self.parents[#self.parents + 1] = self.active
		end

		self.active = item
		local label = build_label(self.active)
		self.sequence = self.sequence == "" and label or self.sequence .. " " .. label
		self.label:set_text(self.sequence)
	else
		print("Error: Invalid action type in keychain:activate")
	end

	-- build keys helper tip
	if keytip then
		if tip_cache[keytip] then
			self.tip = tip_cache[keytip]
		else
			self.tip = awful.util.table.join(build_tip({}, item), build_fake_keys(self.service))
			tip_cache[keytip] = self.tip
		end
		modtip:set_pack(keytip .. " keychain", self.tip, self.style.keytip.column, self.style.keytip.geometry)
	end
end

-- Deactivate last key item
function keychain:undo()
	if #self.parents > 0 then
		self.sequence = self.sequence:sub(1, -(#build_label(self.active) + 2))
		self.label:set_text(self.sequence)

		self.active = self.parents[#self.parents]
		self.parents[#self.parents] = nil
	else
		self:hide()
	end
end

-- Hide widget
function keychain:hide()
	self.wibox.visible = false
	awful.keygrabber.stop(self.keygrabber)
	self.active = nil
	self.parents = {}
	self.sequence = ""
	modtip:remove_pack()
end

return keychain
