---@diagnostic disable: duplicate-set-field
-- 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.

--- Window frame flash overlay for transient success/error/info feedback

local core = require("core")
local command = require("core.command")
local config = require("core.config")
local style = require("core.style")
local hook = require("core.hook")
local WorkspaceView = require("core.workspace.view")
local StatusView = require("core.status.view")

config.frame_flash_enabled = config.frame_flash_enabled ~= false

-- Per-kind style table:
-- default applies to all events; success/error override only those kinds
local base_duration = 0.2
local base_thickness = 4

config.frame_flash_style = config.frame_flash_style or {}
config.frame_flash_style.default = config.frame_flash_style.default or {}
config.frame_flash_style.success = config.frame_flash_style.success or {}
config.frame_flash_style.error = config.frame_flash_style.error or {}

-- Default
if config.frame_flash_style.default.thickness == nil then
	config.frame_flash_style.default.thickness = base_thickness * 2
end
if config.frame_flash_style.default.duration == nil then
	config.frame_flash_style.default.duration = base_duration * 2
end

-- Success
if config.frame_flash_style.success.thickness == nil then
	config.frame_flash_style.success.thickness = base_thickness
end
if config.frame_flash_style.success.duration == nil then
	config.frame_flash_style.success.duration = base_duration
end

-- Error
if config.frame_flash_style.error.thickness == nil then
	config.frame_flash_style.error.thickness = base_thickness * 2
end
if config.frame_flash_style.error.duration == nil then
	config.frame_flash_style.error.duration = base_duration * 2
end

-- Default to line-flash opacity for a consistent intensity curve
config.frame_flash_opacity = config.frame_flash_opacity
	or config.line_flash_opacity
	or config.outline_jump_flash_opacity
	or 0.35

-- Flash success color after successful saves (e.g. Ctrl+S)
config.frame_flash_on_save_success = config.frame_flash_on_save_success ~= false

-- Mirror line-flash-triggered events to frame flash when available
config.frame_flash_from_line_flash = config.frame_flash_from_line_flash ~= false

-- Flash error frame for 'core.error(...)' logs
config.frame_flash_on_core_error = config.frame_flash_on_core_error ~= false

-- Flash error frame for status-view error messages (including direct calls)
config.frame_flash_on_status_error = config.frame_flash_on_status_error or false

local state = {
	time = nil,
	kind = nil,
	color = nil,
}

-- Returns resolved frame style for flash kind using default + optional override
local function get_flash_style(kind)
	local style_cfg = config.frame_flash_style or {}
	local base = style_cfg.default or {}
	local override = {}
	if kind == "success" then
		override = style_cfg.success or {}
	elseif kind == "error" then
		override = style_cfg.error or {}
	end
	local duration = override.duration
	if duration == nil then
		duration = base.duration
	end
	duration = math.max(0.01, duration or 0.16)

	local thickness = override.thickness
	if thickness == nil then
		thickness = base.thickness
	end
	thickness = thickness or 4

	return duration, thickness
end

-- Starts a new frame flash event
local function flash_frame(opts)
	opts = opts or {}
	state.time = system.get_time()
	state.kind = opts.kind
	state.color = opts.color
	core.redraw = true
end

-- Reuse color mapping for semantic kinds
local function flash_kind(kind, color)
	flash_frame({ kind = kind, color = color })
end

-- Mirror line feedback with matching frame color and meaning
hook.add("doc.flash_line", {
	id = "frameflash-line-flash",
	after = function(event)
		if config.frame_flash_enabled and config.frame_flash_from_line_flash and event and event.flash then
			flash_kind(event.flash.kind, event.flash.color)
		end
	end,
})

-- Resolves base frame color from explicit color or semantic kind
local function resolve_color()
	if state.color then
		return state.color
	end
	if state.kind == "error" then
		return style.error or style.notice or style.text
	elseif state.kind == "success" then
		return style.success or style.notice or style.text
	end
	return style.notice or style.accent or style.text
end

-- Draws an inner frame border with fixed pixel thickness
local function draw_inner_frame(x, y, w, h, thickness, color)
	local t = math.max(1, math.floor(thickness or 1))
	if w <= 0 or h <= 0 then
		return
	end
	local side_h = math.max(0, h - t * 2)
	renderer.draw_rect(x, y, w, t, color)
	renderer.draw_rect(x, y + h - t, w, t, color)
	if side_h > 0 then
		renderer.draw_rect(x, y + t, t, side_h, color)
		renderer.draw_rect(x + w - t, y + t, t, side_h, color)
	end
end

-- Flashes success frame only after a successful file save operation
hook.add("doc.save", {
	id = "frameflash-save-success",
	after = function(event)
		if not event or event.ok ~= true then
			return
		end
		if config.frame_flash_enabled and config.frame_flash_on_save_success then
			flash_kind("success", style.success)
		end
	end,
})

-- Flash error frame from the finalized log item instead of wrapping core.error
hook.add("core.log", {
	id = "frameflash-log-error",
	after = function(event)
		if not event or not event.item then
			return
		end
		if event.item.level ~= "error" then
			return
		end
		if config.frame_flash_enabled and config.frame_flash_on_core_error then
			flash_kind("error")
		end
	end,
})

-- Flashes on direct status-view error calls that may bypass 'core.error(...)'
local _original_status_show_error = StatusView.show_error

function StatusView:show_error(text)
	_original_status_show_error(self, text)

	if config.frame_flash_enabled and config.frame_flash_on_status_error then
		flash_kind("error")
	end
end

local _original_root_draw = WorkspaceView.draw

-- Draws and fades the frame flash overlay above the full editor window
function WorkspaceView:draw(...)
	_original_root_draw(self, ...)

	if not (config.frame_flash_enabled and state.time) then
		return
	end

	local duration, thickness = get_flash_style(state.kind)
	local age = system.get_time() - (state.time or 0)
	if age >= duration then
		state.time = nil
		state.kind = nil
		state.color = nil
		return
	end

	local peak = math.max(0, math.min(1, config.frame_flash_opacity or 0.35))
	local alpha = peak * (1 - (age / duration))
	local color = style.transparent(resolve_color(), alpha)
	local w, h = renderer.get_size()
	draw_inner_frame(0, 0, w, h, thickness, color)
	core.redraw = true
end

command.register({
	-- Trigger default frame flash
	["frameflash:default"] = function()
		flash_frame({ kind = nil, color = nil })
	end,
	-- Trigger success-style frame flash
	["frameflash:success"] = function()
		flash_kind("success", style.success)
	end,
	-- Trigger error-style frame flash
	["frameflash:error"] = function()
		flash_frame({ kind = "error" })
	end,
	-- Trigger neutral/info frame flash
	["frameflash:info"] = function()
		flash_frame({ kind = "info", color = style.notice or style.accent or style.text })
	end,
	-- Trigger notice frame flash
	["frameflash:notice"] = function()
		flash_frame({ kind = "notice", color = style.notice or style.accent or style.text })
	end,
	-- Trigger warning frame flash
	["frameflash:warning"] = function()
		flash_frame({ kind = "warning", color = style.warning or style.error or style.notice })
	end,
})
