-- version   = 1.0, 2026-05-19

local hasglyph     = node.has_glyph
local traverse_id  = node.traverse_id
local math_code = node.id("math")

local keyval       = require('luakeyval')
local scan_choice  = keyval.choices
local scan_bool    = keyval.bool
local process_keys = keyval.process

local scan_string  = token.scan_string
local scan_int     = token.scan_int
local scan_keyword = token.scan_keyword

local enabled          = true

local bidi = require('unibidi-lua')
local directiondata   = bidi.directions
local mirrordata      = bidi.mirrors
local brackettypedata = bidi.brackettypes
local setbaselevel    = bidi.setbaselevel
local process         = bidi.node.reorder
local bidiset = bidi.set
bidiset("remove","controls")

local setdata = function(k,v)
    local tbl, scanner
    if k == "setdir" then
        tbl = directiondata
        scanner = scan_string
    elseif k == "setmirror" then
        tbl = mirrordata
        scanner = scan_int
    else
        tbl = brackettypedata
        scanner = scan_string
    end
    local from = scan_int()
    local to = from
    if scan_keyword("-") then
        to = scan_int()
    end
    local data = scanner()
    for cp=from,to do
        tbl[cp] = data
    end
end
local messages = {
    error1 = "unibidi-lua: wrong syntax in \\unibidilua",
    value_forbidden = "unibidi-lua: the %s key does not accept a value",
    value_rquired = "unibidi-lua: the %s key require a value",
}
local keys = {
    enable = { scanner = scan_bool, default = true },
    fences = { scanner = scan_bool, default = true },
    nsm    = { scanner = scan_bool, default = true },
    mirror = { scanner = scan_bool, default = true },
    ["remove"] = {scanner = scan_choice, args = {"none","controls","full"}},
    baselevel = { scanner = scan_string },
    mirrorchar   = { scanner = scan_string },
    startlevel   = { scanner = scan_string },
    setdir = { scanner = function() return true end, func = setdata },
    setmirror = { scanner = function() return true end, func = setdata },
    setbracket = { scanner = function() return true end, func = setdata },
}

local function interface()
    local saved_endlinechar = tex.endlinechar
    tex.endlinechar = 32
    local vals = process_keys(keys,messages)
    tex.endlinechar = saved_endlinechar
    if vals.enable ~= nil then
        enabled = vals.enable
    end
    if vals.fences ~= nil then
        bidiset("fences",vals.fences)
    end
    if vals.remove ~= nil then
        bidiset("remove",vals.remove)
    end
    if vals.nsm ~= nil then
        bidiset("nsm",vals.nsm)
    end
    if vals.mirror ~= nil then
        bidiset("mirror", vals.mirror)
    end
    if vals.baselevel then
        local func, err = load("return " .. vals.baselevel)
        if func then
            bidiset("baselevel",func())
        else
            tex.error("unibidi-lua: error in baselevel", {err})
        end
    end
    if vals.mirrorchar then
        local func, err = load("return " .. vals.mirrorchar)
        if func then
            bidiset("mirrorchar", func())
        else
            tex.error("unibidi-lua: error in mirrorchar", {err})
        end
    end
    if vals.startlevel then
        local func, err = load("return " .. vals.startlevel)
        if func then
            bidiset("startlevel", func())
        else
            tex.error("unibidi-lua: error in startlevel", {err})
        end
    end
end

do
  if token.is_defined('unibidilua') then
      texio.write_nl('log', "unibidi-lua: redefining \\unibidilua")
  end
  local function_table = lua.get_functions_table()
  local luafnalloc = luatexbase and luatexbase.new_luafunction and luatexbase.new_luafunction('unibidilua')
    or #function_table + 1
  token.set_lua('unibidilua', luafnalloc)
  function_table[luafnalloc] = interface
end

-- Just in case we check for balanced math nodes, otherwise the current implementation,
-- which ignores nodes inside math, will have nonsense output.
-- I'm not sure how a list can have unbalanced math nodes,
-- but breqn manages to do that.

local function balance_math(head)
    local stack = 0
    for n, sb in traverse_id(math_code,head) do
        stack = stack + 1-2*sb
        if stack < 0 then
            return false
        end
    end
    if stack ~= 0 then
        return false
    end
    return true
end

local function guarded_process(head,where,direction)
    if not (hasglyph(head) and balance_math(head) and enabled) then 
        return true
    end
    return process(head,direction,where)
end

return {
  reorder = guarded_process
}
