Jump to content

Module:TemplateStarter

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

-- Module:TemplateStarter
-- Generates configurable template structures for new pages using ConfigRepository

local p = {}

-- Required modules
local ConfigRepository = require('Module:ConfigRepository')
local ErrorHandling = require('Module:ErrorHandling')

-- Cache for template lists
local templateListCache = nil
local templateListCacheTime = 0
local CACHE_DURATION = 300 -- 5 minutes

-- Generate empty template wikitext from template type
function p.generateTemplate(templateType)
    -- Input validation and sanitization
    if not templateType or templateType == "" then
        return "Error: Template type is required"
    end
    
    -- Sanitize input: remove any potentially harmful characters
    templateType = mw.text.trim(tostring(templateType))
    
    if not templateType:match("^[%w_%-]+$") then
        return "Error: Invalid template type format"
    end
    
    -- Create error context
    local errorContext = ErrorHandling.createContext("TemplateStarter")
    
    -- Protected function to get configuration
    local function getTemplateConfig()
        local config = ConfigRepository.getConfig(templateType)
        
        if not config then
            return nil, string.format("Template type '%s' not found in ConfigRepository", templateType)
        end
        
        if not config.fields then
            return nil, string.format("Template type '%s' has no fields configuration", templateType)
        end
        
        return config
    end
    
    -- Get configuration with error protection
    local config = ErrorHandling.protect(errorContext, 'getTemplateConfig', getTemplateConfig, nil)
    if not config then
        return "Error: Failed to load template configuration"
    end
    
    -- Pre-allocate lines table for better performance
    local lines = {}
    local lineCount = 0
    
    -- Check for boilerplate configuration
    local hasBoilerplate = config.boilerplate and (config.boilerplate.intro or config.boilerplate.outro)
    
    -- Add debug comment
    if hasBoilerplate then
        lineCount = lineCount + 1
        lines[lineCount] = "<!-- Boilerplate available for " .. templateType .. " template -->"
    else
        lineCount = lineCount + 1
        lines[lineCount] = "<!-- No boilerplate for this template -->"
    end
    
    -- Start the template
    lineCount = lineCount + 1
    lines[lineCount] = "{{" .. templateType
    
    -- Process each field with error protection
    local function processFields()
        for _, field in ipairs(config.fields) do
            -- Skip hidden fields
            if not field.hidden then
                -- Handle fields with multiple keys (prefer single key, fallback to first of multiple)
                local fieldKey = field.key or (field.keys and field.keys[1])
                
                if fieldKey and fieldKey ~= "" then
                    lineCount = lineCount + 1
                    lines[lineCount] = string.format("|%s = ", fieldKey)
                end
            end
        end
        return true
    end
    
    -- Process fields with error protection
    ErrorHandling.protect(errorContext, 'processFields', processFields, false)
    
    -- Close the template
    lineCount = lineCount + 1
    lines[lineCount] = "}}"
    
    -- Add intro boilerplate after template (if available)
    if config.boilerplate and config.boilerplate.intro then
        lineCount = lineCount + 1
        lines[lineCount] = ""  -- First empty line for separation
        lineCount = lineCount + 1
        lines[lineCount] = ""  -- Second empty line for distance
        lineCount = lineCount + 1
        lines[lineCount] = config.boilerplate.intro
    end

    -- Join with newlines
    return table.concat(lines, "\n")
end

-- Main function to be called from wiki (for testing/preview)
function p.main(frame)
    -- Simple validation without complex error handling for this function
    local args = frame.args
    local parent = frame:getParent()
    local pargs = parent and parent.args or {}
    
    -- Get parameters (check both direct and parent args)
    local articleName = args.articleName or pargs.articleName or args[1] or pargs[1]
    local templateType = args.templateType or pargs.templateType or args[2] or pargs[2]
    
    -- Validate inputs
    if not articleName or mw.text.trim(tostring(articleName)) == "" then
        return "Error: Article name is required"
    end
    
    if not templateType or mw.text.trim(tostring(templateType)) == "" then
        return "Error: Template type is required"
    end
    
    -- Generate the template content
    local content = p.generateTemplate(mw.text.trim(tostring(templateType)))
    
    -- For testing, return the generated content in a pre block
    return string.format('<pre>Page: %s\n\n%s</pre>', 
        mw.text.nowiki(mw.text.trim(tostring(articleName))), 
        mw.text.nowiki(content))
end

-- Generate a dynamic preload template (main function used by JS)
function p.preload(frame)
    local args = frame.args
    local templateType = args.templateType or args[1]
    
    if not templateType or mw.text.trim(tostring(templateType)) == "" then
        return "<!-- No template type specified -->"
    end
    
    templateType = mw.text.trim(tostring(templateType))
    
    -- Parse template to check if it's a variant
    local parsedTemplate = p.parseVariantTemplate(templateType)
    
    if parsedTemplate and parsedTemplate.variant then
        -- Generate combined template for variant
        return p.generateCombinedTemplate(parsedTemplate.baseTemplate, parsedTemplate.variant)
    else
        -- Generate regular template
        return p.generateTemplate(templateType)
    end
end

-- Get list of available templates with caching
function p.getAvailableTemplates()
    local currentTime = os.time()
    
    -- Check if cache is valid
    if templateListCache and (currentTime - templateListCacheTime) < CACHE_DURATION then
        return templateListCache
    end
    
    -- No complex error handling here
    local templates = {}
    
    -- Check if ConfigRepository.templates exists
    if ConfigRepository.templates then
        local index = 1
        for templateName, _ in pairs(ConfigRepository.templates) do
            if templateName and templateName ~= "" then
                templates[index] = templateName
                index = index + 1
            end
        end
        table.sort(templates)
    end
    -- No fallback! If ConfigRepository fails, return empty list (single source of truth)

    -- Update cache
    templateListCache = templates
    templateListCacheTime = currentTime
    
    return templates
end

-- Get list of available templates including variants
function p.getAvailableTemplatesWithVariants()
    local templates = p.getAvailableTemplates()
    local templatesWithVariants = {}
    local index = 1
    
    -- Add base templates first
    for _, templateName in ipairs(templates) do
        templatesWithVariants[index] = templateName
        index = index + 1
    end
    
    -- Add variants
    if ConfigRepository.templates then
        for templateName, config in pairs(ConfigRepository.templates) do
            if config.variants then
                for variantKey, variant in pairs(config.variants) do
                    -- Only include active variants
                    if variant.state and variant.name then
                        templatesWithVariants[index] = variant.name
                        index = index + 1
                    end
                end
            end
        end
    end
    
    -- Sort the combined list
    table.sort(templatesWithVariants)
    
    return templatesWithVariants
end

-- Parse variant template name to get base template and variant info
function p.parseVariantTemplate(templateName)
    if not templateName or templateName == "" then
        return nil
    end
    
    -- Check if this is a variant by looking for the pattern in all templates
    if ConfigRepository.templates then
        for baseTemplateName, config in pairs(ConfigRepository.templates) do
            if config.variants then
                for variantKey, variant in pairs(config.variants) do
                    if variant.state and variant.name == templateName then
                        return {
                            baseTemplate = baseTemplateName,
                            variantKey = variantKey,
                            variant = variant
                        }
                    end
                end
            end
        end
    end
    
    -- Not a variant, return as base template
    return {
        baseTemplate = templateName,
        variantKey = nil,
        variant = nil
    }
end

-- Generate combined template for variants
function p.generateCombinedTemplate(baseTemplate, variant)
    if not baseTemplate or not variant then
        return "Error: Invalid template combination"
    end
    
    -- Generate base template
    local baseContent = p.generateTemplate(baseTemplate)
    if not baseContent or baseContent:match("^Error:") then
        return baseContent or "Error: Failed to generate base template"
    end
    
    -- Generate campaign template if specified
    local campaignContent = ""
    if variant.campaign_template then
        campaignContent = string.format("{{#invoke:T-Campaign|render|campaign_name=%s}}", variant.campaign_template)
    end
    
    -- Combine templates with blank line separator
    local combinedLines = {}
    local lineCount = 0
    
    -- Add base template
    lineCount = lineCount + 1
    combinedLines[lineCount] = baseContent
    
    -- Add separator
    lineCount = lineCount + 1
    combinedLines[lineCount] = ""
    
    -- Add campaign template
    if campaignContent ~= "" then
        lineCount = lineCount + 1
        combinedLines[lineCount] = campaignContent
    end
    
    return table.concat(combinedLines, "\n")
end

-- Test function to list available templates (clean output for JS consumption)
function p.listTemplates(frame)
    local templates = p.getAvailableTemplatesWithVariants()
    if #templates == 0 then
        return "No templates available"
    end
    return table.concat(templates, ", ")
end

-- Get creator fields for a specific template type
function p.getCreatorFields(templateType)
    if not templateType or templateType == "" then
        return {}
    end
    
    -- Parse template to resolve variants to base template
    local parsedTemplate = p.parseVariantTemplate(templateType)
    local baseTemplateName = parsedTemplate and parsedTemplate.baseTemplate or templateType
    
    local config = ConfigRepository.getConfig(baseTemplateName)
    return config.creatorFields or {}
end

-- Get field definitions for creator fields
function p.getCreatorFieldDefinitions(templateType)
    if not templateType or templateType == "" then
        return {}
    end
    
    local creatorFields = p.getCreatorFields(templateType)
    local fieldDefinitions = {}
    
    for _, fieldKey in ipairs(creatorFields) do
        local fieldDef = ConfigRepository.pageCreatorFields[fieldKey]
        if fieldDef then
            fieldDefinitions[fieldKey] = fieldDef
        end
    end
    
    return fieldDefinitions
end

-- Replace placeholders in boilerplate text with user values
function p.replacePlaceholders(text, values)
    if not text or not values then
        return text
    end
    
    local result = text
    for key, value in pairs(values) do
        if value and value ~= "" then
            result = result:gsub("%$" .. key .. "%$", value)
        end
    end
    
    return result
end

-- Remove empty placeholders from text (for cases where no values are provided)
function p.removeEmptyPlaceholders(text)
    if not text then
        return text
    end
    
    -- Remove any remaining $VARIABLE$ placeholders that weren't filled
    local result = text:gsub("%$[A-Z_]+%$", "")
    
    -- Clean up any resulting double spaces or awkward punctuation
    result = result:gsub("%s+", " ")  -- Multiple spaces to single space
    result = result:gsub("^%s+", "")  -- Leading whitespace
    result = result:gsub("%s+$", "")  -- Trailing whitespace
    result = result:gsub("%s+%.", ".")  -- Space before period
    result = result:gsub("is a%s+based", "is based")  -- Fix "is a  based" to "is based"
    result = result:gsub("is a%s+in", "is in")  -- Fix "is a  in" to "is in"
    
    return result
end

-- Generate template with placeholder replacement
function p.generateTemplateWithValues(templateType, values)
    if not templateType or templateType == "" then
        return "Error: Template type is required"
    end
    
    -- Get base template
    local baseTemplate = p.generateTemplate(templateType)
    
    -- Get boilerplate and replace placeholders
    local config = ConfigRepository.getConfig(templateType)
    if config.boilerplate and config.boilerplate.intro then
        local processedIntro = p.replacePlaceholders(config.boilerplate.intro, values)
        -- Replace the boilerplate in the template
        baseTemplate = baseTemplate:gsub(config.boilerplate.intro, processedIntro)
    end
    
    return baseTemplate
end

-- Get creator field definitions as JSON for JavaScript consumption
function p.getCreatorFieldDefinitionsJSON(frame)
    local args = frame.args
    local templateType = args.templateType or args[1]
    
    if not templateType or templateType == "" then
        return "{}"
    end
    
    local fieldDefinitions = p.getCreatorFieldDefinitions(templateType)
    
    -- Convert to JSON-like string manually (simple approach)
    local jsonParts = {}
    for fieldKey, fieldDef in pairs(fieldDefinitions) do
        local fieldJson = string.format(
            '"%s":{"key":"%s","label":"%s","placeholder":"%s","required":%s}',
            fieldKey,
            fieldDef.key or "",
            fieldDef.label or "",
            fieldDef.placeholder or "",
            fieldDef.required and "true" or "false"
        )
        table.insert(jsonParts, fieldJson)
    end
    
    return "{" .. table.concat(jsonParts, ",") .. "}"
end

return p