Module:Guide

Documentation for this module may be created at Module:Guide/doc

-- Module:Guide
-- Renders a guide page with article path, cover image, editors, sources, and sidebar.
-- Sidebar lists all other guides — fixed nil rendering of guide titles.
local p = {}

-- Helper message extractor
local function getMessage(msg)
    return mw.message.new(msg):plain()
end

-- Helper function to format a list of items as formatted links
local function formatted_list(items, prefix)
    local formatted_items = {}

    if type(items) == "table" then
        for _, item in ipairs(items) do
            item = mw.text.trim(item)
            table.insert(formatted_items, '[[' .. item .. ']]')
        end
    else
        for item in items:gmatch("[^;]+") do
            item = mw.text.trim(item)
            table.insert(formatted_items, '[[' .. item .. ']]')
        end
    end

    local formatted_list
    if #formatted_items > 1 then
        local last_item = table.remove(formatted_items)
        formatted_list = table.concat(formatted_items, ", ") .. " and " .. last_item
    else
        formatted_list = formatted_items[1] or ""
    end

    if prefix and prefix ~= "" then
        formatted_list = prefix .. formatted_list
    end

    return formatted_list
end

function p.main(frame)
    local getTitle          = mw.title.getCurrentTitle()
    local page              = getTitle.fullText
    local template          = frame:getParent().args
    local category          = '[[Category:Guides]]'

    local display_name      = template['Display name'] or page or ''
    local teaser            = template['About'] or ''
    local guide_editor      = template['Guide editor'] or ''
    local associate_editor  = template['Associate editor'] or ''
    local source            = template['Source'] or ''
    local cover_image       = template['Cover image'] or ''

    local filename         = ''
    if cover_image ~= '' then
         filename = frame:preprocess('File:{{PAGENAME:' .. cover_image .. '}}') or ''
    end

    local suppress_caption = template['Suppress uploaded media caption'] or 'No'
    local caption          = ''
    if suppress_caption == 'Yes' then
         caption = template['Cover image caption'] or ''
    else
         caption = tostring(frame:callParserFunction('#ask',
             '[[' .. filename .. ']][[License short::+]]',
             '?License URL',
             '?License short',
             '?Media credits URL',
             '?Media credits text',
             '?Other text',
             'named args=yes',
             'format=plainlist',
             'mainlabel=-',
             'headers=hide',
             'link=none',
             'template=Image attribution'
          ))
    end

    local limit_img_width  = template['Limit uploaded media width'] or 'No'
    local limit            = template['Limit'] or ''
    local img_width        = ''
    if limit_img_width == 'Yes' and limit ~= '' then
        img_width = limit .. 'px'
    end

    if display_name ~= '' then
        frame:preprocess('{{DISPLAYTITLE:' .. display_name .. '|noreplace|noerror}}')
        frame:preprocess('{{DEFAULTSORT:' .. display_name .. '}}')
    end

    local articles         = template['Articles'] or {}

    local set_properties   = frame:callParserFunction('#set',
        'Teaser=' .. teaser,
        'Cover image=' .. filename,
        'Guide editor=' .. guide_editor,
        '+sep=;',
        'Associate editor=' .. associate_editor,
        '+sep=;',
        'Source=' .. source,
        '+sep=;'
    )

    local html = mw.html.create()

    -- ARTICLE PATH
    local articlepath = html:tag('div')
        :attr('id', 'article-path')
        :addClass('interface d-flex align-items-center justify-content-between')
        :addClass('font-weight-bold text-uppercase d-print-none screenonly noprint')

    articlepath:tag('span')
        :attr('id', 'observatory-path')
        :addClass('d-print-none noprint')
        :wikitext('[[' ..
            frame:preprocess('{{int:mainpage}}|{{SITENAME}}') ..
            ']] » ' ..
            '[[Guides]]'
        )

    local areas = mw.smw.ask{
        '[[' .. page .. ']]',
        'mainlabel=-',
        '?Path area#-=Area'
    } or {}

    local searchCategory = 'Guides'
    local deepSearchCategory = 'Guides'

    local areaNames = {}
    if #areas > 0 then
        if type(areas[1]['Area']) == "table" then
            for _, area in ipairs(areas[1]['Area']) do
                table.insert(areaNames, area)
            end
        else
            table.insert(areaNames, areas[1]['Area'])
        end
        searchCategory = table.concat(areaNames, ',')
        deepSearchCategory = table.concat(areaNames, '%22%2C%22')
    end

    articlepath:tag('span'):wikitext(frame:expandTemplate({
        title = 'Article buttons', args = {
            ['Area'] = searchCategory,
            ['DeepArea'] = deepSearchCategory
        }
    }))

    html:tag('div')
        :addClass('d-none')
        :wikitext()

    if cover_image ~= '' then
        html:tag('div')
            :attr('id', 'cover-image')
            :wikitext('[[' .. filename .. '|thumb|' .. caption .. '|' .. img_width .. ']]')
    end

    local authorship = html:tag('div')
        :addClass('d-flex w-100 flex-column flex-md-row justify-content-between')

    local editors = authorship:tag('div')
        :addClass('interface')

    editors:tag('div'):wikitext(formatted_list(guide_editor, 'Editor: '))

    if associate_editor and associate_editor ~= '' then
        editors:tag('div'):wikitext(formatted_list(associate_editor, 'Associate Editor: ')):done()
    end

    authorship:tag('div')
        :addClass('interface mb-3')
        :wikitext(formatted_list(source, 'Source: '))

    html:tag('div')
        :addClass('mt-3 mb-5 text-muted')
        :newline()
        :wikitext(teaser)

    html:tag('div')
        :wikitext(articles)

    -- Initialize the sideblock — list all other guides
    local sideblock = html:tag('div')
        :attr('id', 'article-sources')
        :addClass('my-4 text-left interface')
        :css('flex', '0 0 280px')

    -- FIX: Use table-form query with mainlabel to get page titles reliably
    local all_guides = mw.smw.ask {
        '[[Category:Guides]][[!' .. page .. ']]',
        'mainlabel=-',
        '?#-=title',
        'limit=500',
        'searchlabel='
    } or {}

    if #all_guides > 0 then
        for _, guide in ipairs(all_guides) do
            -- guide.title is the page title (named via ?#-=title)
            -- fallback to guide[1] for backward compat
            local guideTitle = guide.title or guide[1] or ''
            if guideTitle == '' or guideTitle == nil then
                -- skip entries with no title
            else
                sideblock:tag('div')
                    :addClass('py-1')
                    :wikitext('<i class="far fa-map mr-2"></i>[[' .. tostring(guideTitle) .. ']]')
            end
        end
    end

    local guide_output = tostring(html) .. tostring(sideblock) .. category

    frame:callParserFunction(
       '#counter',
       'stepcount',
       'type=numeric',
       'set=0'
    )
    return guide_output
end

function p.test()
    local areas = mw.smw.ask{
        '[[Guide to Animals in Science and Research]]',
        'mainlabel=-',
        '?Path area#-=Area'
    } or {}
    return areas
end

return p