-- 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.

local core = require("core")
local command = require("core.command")
local config = require("core.config")
local style = require("core.style")
local fps_overlay = require("core.ui.fps_overlay")
local DocView = require("core.doc.view")
local tokenizer = require("core.format.tokenizer")
local zoom = require("core.ui.zoom")

--- Resolves active document from core state for syntax trace commands
-- @return table|nil active document when available
local function get_active_document_for_trace()
	local active_document = core.active_doc
	if active_document then
		return active_document
	end

	local active_view = core.active_view
	return active_view and active_view.doc or nil
end

--- Runs synchronous tokenization across one document for trace capture
-- @param active_document table document instance
-- @return number processed line count
local function tokenize_document_for_trace(active_document)
	local line_count = (active_document and active_document.lines and #active_document.lines) or 0
	if line_count < 1 then
		return 0
	end

	local highlighter = active_document and active_document.highlighter or nil
	if not highlighter then
		local format_definition = active_document and active_document.format or nil
		if not format_definition then
			return 0
		end

		local state = nil
		for line_index = 1, line_count do
			local line_text = active_document.lines[line_index] or ""
			local next_line_text = active_document.lines[line_index + 1]
			_, state = tokenizer.tokenize(format_definition, line_text, state, next_line_text)
		end
		return line_count
	end

	if type(highlighter.reset) == "function" then
		highlighter:reset()
	elseif type(highlighter.invalidate) == "function" then
		highlighter:invalidate(1)
	end

	if type(highlighter.tokenize_range) == "function" then
		highlighter:tokenize_range(1, line_count, nil)
		return line_count
	end

	if type(highlighter.get_line) == "function" then
		for line_index = 1, line_count do
			highlighter:get_line(line_index)
		end
		return line_count
	end

	return 0
end

command.register({

	["debug:gc"] = function()
		collectgarbage("collect")
	end,

	-- Logs Lua runtime/version details and feature availability probes
	["debug:lua-info"] = function()
		-- Delegate debug command body to centralized core.debug module
		core.debug.log_lua_info(core, function()
			command.perform("log:open")
		end)
	end,

	-- Logs lazy fallback-font counters for key UI fonts
	["debug:fallback-stats"] = function()
		-- Delegate debug command body to centralized core.debug module
		core.debug.log_fallback_stats(core, style, function()
			command.perform("log:open")
		end)
	end,

	["core:dump-syntax-render-perf"] = function()
		core.debug.dump_syntax_render_perf(core, config, function()
			command.perform("log:open")
		end)
	end,

	-- Logs current scaled-font cache state from the core-owned zoom service
	["debug:scale-cache-status"] = function()
		local status = zoom.get_scale_font_cache_status()
		local count = status.count or 0
		local max_count = status.max_count or 0
		local bytes = status.bytes or 0
		local max_bytes = status.max_bytes or 0
		local max_bytes_text = max_bytes <= 0 and "unlimited" or string.format("%d", max_bytes)
		core.notice(
			"[Debug] scale cache: enabled=%s entries=%d/%d bytes=%d/%s",
			tostring(status.enabled),
			count,
			max_count,
			bytes,
			max_bytes_text
		)
		command.perform("log:open")
	end,

	-- Toggles Browser debug logging
	["debug:toggle-browser-logging"] = function()
		-- Delegate Browser debug toggle to centralized core.debug module
		core.debug.toggle_browser_logging(core, config)
	end,

	-- Runs tests/*.lua immediately, ignoring startup autorun toggle
	["debug:run-tests"] = function()
		-- Delegate test execution to centralized core.debug module
		core.debug.run_tests(core, config, { force = true })
	end,

	-- Toggle DocView lane overlay diagnostics
	["debug:toggle-debug-lanes"] = function()
		config.debug_lanes = not config.debug_lanes
		core.redraw = true
	end,

	-- Toggle the core-owned frame-rate display
	["fps-overlay:toggle"] = function()
		fps_overlay.toggle(core)
	end,

	-- Run lightweight doc-pane lane and scroll invariants against active view
	["debug:doc-pane-smoke"] = function()
		local av = core.active_view
		if not (av and av.is and av:is(DocView)) then
			core.error("Doc pane smoke: active view is not a DocView")
			return
		end

		local function check(ok, msg)
			if not ok then
				error(msg)
			end
		end

		local restore_to_x = av.scroll.to.x or 0
		local restore_to_y = av.scroll.to.y or 0
		local restore_x = av.scroll.x or 0
		local restore_y = av.scroll.y or 0
		local lane_id = "__doc_pane_smoke_tmp__"

		--- Restore temporary lane and scroll state after smoke run
		local function restore_view()
			av:set_lane_size("right", lane_id, 0)
			av.scroll.to.x = restore_to_x
			av.scroll.to.y = restore_to_y
			av.scroll.x = restore_x
			av.scroll.y = restore_y
		end

		local completed, smoke_error = pcall(function()
			local lane_size = math.max(1, math.floor(12 * SCALE + 0.5))
			av:set_lane_size("right", lane_id, lane_size, -9999)

			local rx, ry, rw, rh = av:get_lane_rect("right", lane_id)
			check(rx and rw and rh and rw >= lane_size and rh > 0, "lane rect allocation failed")
			av:set_lane_size("right", lane_id, 0)
			check(av:get_lane_size("right", lane_id) == 0, "lane cleanup failed")

			av.scroll.to.x = 1e9
			av.scroll.to.y = 1e9
			av.scroll.x = 1e9
			av.scroll.y = 1e9
			av:clamp_scroll_position()

			local max_x = av:get_scroll_max_x()
			local max_y = av:get_scroll_max_y()

			if max_x ~= math.huge then
				check(av.scroll.to.x <= max_x + 1e-6, "scroll.to.x exceeds max_x")
				check(av.scroll.x <= max_x + 1e-6, "scroll.x exceeds max_x")
			else
				check(av.scroll.to.x >= 0 and av.scroll.x >= 0, "scroll.x must stay non-negative")
			end
			check(av.scroll.to.y <= max_y + 1e-6, "scroll.to.y exceeds max_y")
			check(av.scroll.y <= max_y + 1e-6, "scroll.y exceeds max_y")
		end)

		restore_view()
		if not completed then
			error(smoke_error, 0)
		end

		core.log_quiet("[Doc Pane Smoke] ok: lane+scroll invariants passed")
		command.perform("log:open")
	end,

		-- Starts trace for current document syntax, tokenizes current doc, then stops trace
		["debug:syntax-trace-current-doc"] = function()
			local active_document = get_active_document_for_trace()
			if not active_document then
				core.error("Syntax trace current-doc failed: no active document")
				return
			end

			local format_definition = active_document.format
			local module_name = tokenizer.trace_get_syntax_id(format_definition)
			if type(module_name) ~= "string" or module_name == "" or module_name == "syntax" then
				core.error("Syntax trace current-doc failed: active doc has no format module id")
				return
			end

			local started, trace_path = tokenizer.trace_start({ module = module_name })
			if not started then
				core.error("Syntax trace current-doc failed: %s", tostring(trace_path or "unknown error"))
				return
			end

			local processed_line_count = 0
			local tokenized_ok, tokenize_error = pcall(function()
				processed_line_count = tokenize_document_for_trace(active_document)
			end)
			tokenizer.trace_stop()

			if not tokenized_ok then
				core.error("Syntax trace current-doc failed: %s", tostring(tokenize_error or "tokenization failed"))
				return
			end

			core.redraw = true
			core.success(
				"[Debug] Syntax trace finished: module=%s lines=%d path=%s",
				module_name,
				processed_line_count,
				tostring(trace_path)
			)
		end,

		-- Starts syntax trace logging; current doc syntax filter when available, all modules otherwise
		["syntax-trace-start"] = function()
			local active_document = get_active_document_for_trace()
			local format_definition = active_document and active_document.format or nil
			local module_name = tokenizer.trace_get_syntax_id(format_definition)
			local trace_options = {}
			local trace_scope = "*"
			if type(module_name) == "string" and module_name ~= "" and module_name ~= "syntax" then
				trace_options.module = module_name
				trace_scope = module_name
			end

			local started, trace_path = tokenizer.trace_start(trace_options)
			if not started then
				core.error("Syntax trace start failed: %s", tostring(trace_path or "unknown error"))
				return
			end

			core.notice("[Debug] Syntax trace started: module=%s path=%s", trace_scope, tostring(trace_path))
		end,

		-- Stops active syntax trace logging
		["syntax-trace-stop"] = function()
			local stopped = tokenizer.trace_stop()
			if stopped then
				core.notice("[Debug] Syntax trace stopped")
			else
				core.notice("[Debug] Syntax trace was not active")
			end
		end,

		-- Logs current syntax trace status
		["syntax-trace-status"] = function()
			local status = tokenizer.trace_status()
			core.notice(
				"[Debug] Syntax trace status: enabled=%s module=%s path=%s",
				tostring(status.enabled == true),
				tostring(status.module or "*"),
				tostring(status.path or "nil")
			)
		end,
	})
