-- lx-bundle:true

--- XLSX extraction commands for binary-viewer documents.
-- @module plugins.xlsxutils

local core = require("core")
local command = require("core.command")
local Doc = require("core.doc")

local reader

--- Load the XLSX reader only when extraction needs it
-- @return table reader_module
local function get_reader()
	if not reader then
		reader = require("plugins.xlsxutils.reader")
	end

	return reader
end

--- Return the active binary XLSX document by declared format or filename
-- @return table|nil doc
local function active_xlsx_doc()
	local view = core.active_view
	local doc = view and view.doc
	local filename = doc and doc.filename
	local declared_type = doc and type(doc.format) == "table" and doc.format.office_type or nil
	local lower_filename = type(filename) == "string" and filename:lower() or ""

	local declared_format_matches = declared_type == "xlsx" or declared_type == "xlsm"
	local filename_matches = declared_type == nil
		and (lower_filename:sub(-5) == ".xlsx" or lower_filename:sub(-5) == ".xlsm")

	if doc and doc.is_binary_placeholder == true and (declared_format_matches or filename_matches) then
		return doc
	end

	return nil
end

--- Return whether the active view contains a supported binary XLSX document
-- @return boolean active
local function is_active_xlsx()
	return active_xlsx_doc() ~= nil
end

--- Open or replace one extracted XLSX output
-- @param source_doc table source Office document
-- @param content string extracted content
-- @param suffix string generated filename suffix
-- @param context table|nil command display options
-- @return boolean opened
local function open_output_doc(source_doc, content, suffix, context)
	local doc = Doc.create((source_doc.filename or "untitled") .. suffix)
	local last_line = #doc.lines
	local last_col = #(doc.lines[last_line] or "") + 1
	doc:remove(1, 1, last_line, last_col)
	doc:insert(1, 1, content)
	doc:set_selection(1, 1)
	doc.save_as_required = true
	doc:clean()

	local view_options = {
		open_mode = context and context.open_mode or nil,
		word_wrap = false,
	}
	local view

	if context and context.replace_view then
		view = core.workspace_view:replace_doc_view(context.replace_view, doc, view_options)
	else
		view = core.workspace_view:open_doc(doc, view_options)
	end

	return view ~= nil
end

--- Extract the active XLSX document into one generated document
-- @param mode string text, markdown, or tsv
-- @param context table|nil command display options
-- @return boolean extracted
local function extract(mode, context)
	local doc = active_xlsx_doc()
	if not doc then
		core.error("XLSX extraction requires an active binary XLSX document")
		return false
	end

	local content, err
	if mode == "markdown" then
		content, err = get_reader().extract_markdown(doc.filename)
	elseif mode == "tsv" then
		content, err = get_reader().extract_tsv(doc.filename)
	else
		content, err = get_reader().extract_text(doc.filename)
	end

	if not content then
		core.error("XLSX extraction failed: %s", tostring(err))
		return false
	end

	local suffix = mode == "markdown" and ".extracted.md"
		or mode == "tsv" and ".extracted.tsv"
		or ".extracted.txt"
	if not open_output_doc(doc, content, suffix, context) then
		core.error("XLSX extraction succeeded, but its document could not be displayed")
		return false
	end
	core.log(
		'Extracted "%s" as %s',
		doc.filename,
		mode == "markdown" and "Markdown" or mode == "tsv" and "TSV" or "text"
	)
	return true
end

command.register({
	["xlsx:extract-text"] = {
		predicate = is_active_xlsx,
		perform = function(context)
			return extract("text", context)
		end,
	},

	["xlsx:extract-md"] = {
		predicate = is_active_xlsx,
		perform = function(context)
			return extract("markdown", context)
		end,
	},

	["xlsx:extract-tsv"] = {
		predicate = is_active_xlsx,
		perform = function(context)
			return extract("tsv", context)
		end,
	},
})

--- Read one XLSX file on demand
-- @param ... any reader arguments
-- @return ... any reader results
local function read(...)
	return get_reader().read(...)
end

--- Extract plain text from one XLSX file on demand
-- @param ... any reader arguments
-- @return ... any reader results
local function extract_text(...)
	return get_reader().extract_text(...)
end

--- Extract Markdown from one XLSX file on demand
-- @param ... any reader arguments
-- @return ... any reader results
local function extract_markdown(...)
	return get_reader().extract_markdown(...)
end

--- Extract TSV from one XLSX file on demand
-- @param ... any reader arguments
-- @return ... any reader results
local function extract_tsv(...)
	return get_reader().extract_tsv(...)
end

return {
	read = read,
	extract_text = extract_text,
	extract_markdown = extract_markdown,
	extract_tsv = extract_tsv,
}
