-- lx-bundle:true

-- This file is part of Lx, created by Liqube <www.liqube.com>
-- Copyright Liqube Audio. All rights reserved.
-- Lx Interim License. See LICENSE for details.

--- ANSi art preview
-- @module plugins.ansipreview

local core = require("core")
local command = require("core.command")
local common = require("core.common")
local config = require("core.config")
local hook = require("core.hook")
local keymap = require("core.keymap")
local open_handler = require("core.open_handler")

-- Palette validation waits until an ANSi preview opens
config.plugins.ansipreview = common.merge({ palette = "vga" }, config.plugins.ansipreview)

-- Weak values avoid keeping closed previews alive while an arrow key is held
local held_line_views = setmetatable({}, { __mode = "v" })

--- Return whether the focused view is an ANSi preview
-- @return boolean active
local function is_active_ansi_preview()
	local active_view = core.active_view

	return active_view and active_view.is_ansi_preview == true or false
end

--- Create an ANSi preview only when a matching file needs it
-- @param request table shared open request
-- @return table view ANSi preview
local function create_preview(request)
	local AnsiPreview = require("plugins.ansipreview.view")

	return AnsiPreview(request.doc, request.options)
end

-- The DOS detector must accept the file before this format-specific handler can run
open_handler.register({
	name = "ansiart",
	formats = { "dos" },
	modes = { "preview" },
	create_view = create_preview,
})

--- Start or continue one held arrow movement
-- @param direction number negative for up and positive for down
-- @param fallback_key string key used outside normal keymap dispatch
-- @param context table|nil command input details
-- @return boolean handled
local function press_line_key(direction, fallback_key, context)
	local view = core.active_view
	local key = type(context) == "table" and context.key or fallback_key
	key = tostring(key or fallback_key)
	held_line_views[key] = view
	view:set_line_scroll_key(key, direction, true)

	return true
end

--- Select and report the next or previous keyboard movement preset
-- @param direction number negative for previous and positive for next
-- @return boolean handled
local function cycle_motion_preset(direction)
	local preset_name = core.active_view:cycle_motion_preset(direction)
	core.log("ANSi motion preset: %s", preset_name)

	return true
end

-- Key release ends continuous movement even when the operating system repeated key-down events
hook.add("core.event", {
	id = "plugins.ansipreview.line_release",
	keyup = function(payload)
		local key = payload and payload.args and payload.args[1]
		local view = key and held_line_views[key] or nil

		if view and type(view.release_line_scroll_key) == "function" then
			view:release_line_scroll_key(key)
		end

		if key then
			held_line_views[key] = nil
		end
	end,
})

command.register({
	-- Build continuous movement while an arrow key is held
	["ansi-preview:line-up"] = {
		predicate = is_active_ansi_preview,
		perform = function(context)
			return press_line_key(-1, "up", context)
		end,
	},
	["ansi-preview:line-down"] = {
		predicate = is_active_ansi_preview,
		perform = function(context)
			return press_line_key(1, "down", context)
		end,
	},

	-- Move by one visible page
	["ansi-preview:page-up"] = {
		predicate = is_active_ansi_preview,
		perform = function()
			local view = core.active_view
			view:scroll_page_by(-view:get_page_scroll_delta())
			return true
		end,
	},
	["ansi-preview:page-down"] = {
		predicate = is_active_ansi_preview,
		perform = function()
			local view = core.active_view
			view:scroll_page_by(view:get_page_scroll_delta())
			return true
		end,
	},

	-- Start or gently stop continuous edge-to-edge scrolling
	["ansi-preview:toggle-auto-scroll"] = {
		predicate = is_active_ansi_preview,
		perform = function()
			core.active_view:toggle_auto_scroll()
			return true
		end,
	},

	-- Move to the first or last row
	["ansi-preview:home"] = {
		predicate = is_active_ansi_preview,
		perform = function()
			core.active_view:scroll_to_ratio(0)
			return true
		end,
	},
	["ansi-preview:end"] = {
		predicate = is_active_ansi_preview,
		perform = function()
			core.active_view:scroll_to_ratio(1)
			return true
		end,
	},

	-- Switch movement settings live while comparing their feel
	["ansi-preview:previous-motion-preset"] = {
		predicate = is_active_ansi_preview,
		perform = function()
			return cycle_motion_preset(-1)
		end,
	},
	["ansi-preview:next-motion-preset"] = {
		predicate = is_active_ansi_preview,
		perform = function()
			return cycle_motion_preset(1)
		end,
	},
})

-- Keep ANSi navigation inside the ANSi preview
keymap.bind("plugin", {
	ansi_preview = {
		["up"] = "ansi-preview:line-up",
		["down"] = "ansi-preview:line-down",
		-- ANSi art uses Space for continuous viewing while Shift+Space still pages upward
		["shift+space"] = "ansi-preview:page-up",
		["space"] = "ansi-preview:toggle-auto-scroll",
		["pageup"] = "ansi-preview:page-up",
		["pagedown"] = "ansi-preview:page-down",
		["home"] = "ansi-preview:home",
		["end"] = "ansi-preview:end",
		["ctrl+home"] = "ansi-preview:home",
		["ctrl+end"] = "ansi-preview:end",
		["ctrl+shift+pageup"] = "ansi-preview:previous-motion-preset",
		["ctrl+shift+pagedown"] = "ansi-preview:next-motion-preset",
	},
})
