-- This program scans a text for REGIONS to sort.
--
-- Each REGION contains the following:
--      A "START_SORT:" string command that also tells the sorter which types of HEADLINES the REGION contains.
--      (optionally) a number of PREFACE lines.
--      A number of SECTIONS to sort
--      An "END_SORT" command
--
-- Each SECTION contains the following
--      A HEADLINE
--      A number of lines of body text.
--
-- A HEADLINE can (currently) be one of:
--      - An asciidoc headline: a line that always begins with a number of '=' characters, followed by at least one normal text character.
--          These headlines are convenient because it allows us to easily alphabetize headlines in certain chapters
--          (for instance in chapters where spells, items, or abilities each have their own headline).
--      - A hidden headline. A text they always begins "//HEADLINE:", followed by a number of normal text characters.
--          These hidden headlines are useful when we want to alphabetize tables, lists, and other sections that do not contain
--          explicit headlines.
--
--
-- Each REGION has a number of SECTIONS, and each SECTION
-- has a HEADLINE. HEADLINES can be actual asciidoc headlines (strings beginning with a number of "=" characters)
-- or they can be pseudo-headlines that exist as comments inside the asciidoc file.
--
--
-- {{{ Some variables to help us
local output = ""            -- Output buffer
local STATE_SCANNING = 0     -- State machine: scanning for START_SORT commands
local MODE_SORTING = 1       -- State machine: sorting, and looking for END_SORT commands.
local state = STATE_SCANNING -- State machine: current mode
local headlinePattern = ""   -- If a line matches this pattern, is a section headline (a sort key)
local sections = {}          -- The sections we're currently sorting.
local preface = ""           -- contains the text in a chunk that comes before the first section headline
--}}}

--{{{ _start_sort(line)
---Check if a line starts with START_SORT and then a key to look for.
---@param line string The line we're checking
---
---@return boolean success Was the line correctly formatted?
---@return boolean start_sort Did this line contain a correct START_SORT command?
---@return string  pattern The regex pattern to scan for to find the headline/key that each section starts with.
---
local function _is_start_sort_command(line)
    local match = string.match(line, "^//%s*START_SORT%s*(%S*)%s*$")

    if not match then
        return true, false, ""
    end

    --
    -- ASCIIDOC HEADLINE
    --
    -- These types of headlines start with a number of "=" characters.
    -- They sort REGIONS of text where each SECTION begins with an asciidoc headline
    if string.sub(match, 1, 1) == "=" then
        -- We are sorting by section names
        -- The type of section we're sorting (i.e. its depth) is denoted by the given number '=' characters
        -- Every time we encounter a section with the corresponding number of equal signs, we consider that a sorting key.
        return true, true, "^" .. match .. "%s+"
    end

    --
    -- HIDDEN HEADLINE
    --
    -- These headlines begin with "//HEADLINE:"
    -- They are comments inside the asciidoc source code so they do not show up in the rendered text.
    -- Thus they sort REGIONS of text where each SECTION begins with a hidden headline
    if match == "//HEADLINE:" then
        -- We are sorting by custom keys.
        -- This means that every time we encounter the string "HEADLINE:" we consider the next line a sorting key.
        return true, true, "^%s*//HEADLINE:"
    end

    return false, false, match
end
--}}}

-- {{{ _state_machine_scan(line)
local function _state_machine_scan(line)
    local success, is_start_sort_command, _headline_pattern = _is_start_sort_command(line)

    if not success then
        local errMsg = string.format("Failed to parse sort key: '%s'", _headline_pattern)
        print(errMsg)
        error(errMsg)
    end

    if is_start_sort_command then
        state = MODE_SORTING
        headlinePattern = _headline_pattern
        sections = {}
        preface = ""
    end

    output = output .. line
end
--}}}

--{{{ _state_machine_sort(line)
local function _state_machine_sort(line)
    --
    -- NEW SECTION STARTED
    --
    if string.match(line, headlinePattern) then -- We found a new section headline/sortkey
        -- Start a new section
        sections[#sections + 1] = line

        return
    end

    --
    -- SORTED REGION ENDED
    --
    if string.match(line, "^//%s*END_SORT") then
        -- We have encountered a command to stop sorting. So we should stop sorting.
        -- Each section within the current sorting area is a single string
        -- Sort those strings to sort the entire area,
        -- and compile/combine/join the strings into the output.

        table.sort(sections)
        local sorted = table.concat(sections, "")

        -- Compile the resulting sorted text.
        output = output .. preface .. sorted .. line

        -- Reset the state machine to start looking for new areas to sort.
        state = STATE_SCANNING
        sections = {}
        preface = ""

        return
    end

    -- SECTION PREFACE
    --
    -- This happens if we have preface text before the first heading/sortkey
    -- Preface text is paragraph text that comes after START_SORT, but before the
    -- first sorting key/section is encountered.
    if nil == sections[#sections] then
        preface = preface .. line

        return
    end

    -- This line is a normal line within the section.
    -- Add it to the current section.
    sections[#sections] = sections[#sections] .. line
end
-- }}}

--{{{ _state_machine_process_single_line(line)
local function _state_machine_process_single_line(line)
    line = line .. "\n"

    -- SCANNING
    if state == STATE_SCANNING then
        _state_machine_scan(line)
        return
    end

    --
    -- SORTING
    --
    if state == MODE_SORTING then
        _state_machine_sort(line)
        return
    end
end
--}}}

--{{{ sorter(lines)
---Scan a file and sort the headings of the chunks marked for sorting
---@param lines Iterator
---@return string output sorted string
local function sorter(lines)
    for line in lines do
        _state_machine_process_single_line(line)
    end

    if not (state == STATE_SCANNING and #sections == 0) then
        error("you must be missing an END_SORT or similar")
    end

    return output
end
--}}}

return sorter
