Немає опису редагування
Мітки: Ручний відкіт Скасовано
Немає опису редагування
Мітка: Ручний відкіт
 
(Не показано 17 проміжних версій цього користувача)
Рядок 2: Рядок 2:


-- ================================================
-- ================================================
-- ГЛОБАЛЬНЕ КЕШУВАННЯ
-- КЕШУВАННЯ
-- ================================================
-- ================================================


local page_cache = {}
local page_cache = {}
local table_cache = {}
local table_cache = {}
-- ================================================
-- УТИЛІТИ
-- ================================================
local function clean_wikilinks(text)
    if not text then return nil end
    text = mw.ustring.gsub(text, "%[%[([^%]|]+)|([^%]]+)%]%]", "%2")
    text = mw.ustring.gsub(text, "%[%[([^%]]+)%]%]", "%1")
    return mw.text.trim(text)
end
local function error_output(message)
    return '<span style="color:red; font-weight:bold;">Помилка: ' .. message .. '</span>'
end


local function get_page_content(page_name)
local function get_page_content(page_name)
Рядок 27: Рядок 12:
         return page_cache[page_name]
         return page_cache[page_name]
     end
     end
   
 
     local title = mw.title.new(page_name)
     local title = mw.title.new(page_name)
     if not title or not title.exists then
     if not title or not title.exists then
        page_cache[page_name] = nil
         return nil
         return nil
     end
     end
   
 
     local content = title:getContent()
     local content = title:getContent()
     page_cache[page_name] = content
     page_cache[page_name] = content
Рядок 42: Рядок 28:
         return table_cache[page_name]
         return table_cache[page_name]
     end
     end
   
 
     local content = get_page_content(page_name)
     local content = get_page_content(page_name)
     if not content then return nil end
     if not content then
   
        table_cache[page_name] = nil
        return nil
    end
 
     local table_start = mw.ustring.find(content, "{|")
     local table_start = mw.ustring.find(content, "{|")
     local table_end = mw.ustring.find(content, "|}", table_start)
     local table_end = mw.ustring.find(content, "|}", table_start)
   
 
     if not table_start or not table_end then return nil end
     if not table_start or not table_end then
   
        table_cache[page_name] = nil
        return nil
    end
 
     local table_content = mw.ustring.sub(content, table_start, table_end + 1)
     local table_content = mw.ustring.sub(content, table_start, table_end + 1)
     table_cache[page_name] = table_content
     table_cache[page_name] = table_content
     return table_content
     return table_content
end
-- ================================================
-- УТИЛІТИ
-- ================================================
local function error_output(context, error_details)
    local safe_details = error_details or "N/A"
    return string.format("<span style='color:red; font-weight:bold;'>[Error: %s. Details: %s]</span>",
        context or "Unknown", safe_details)
end
-- ШВИДКА ВЕРСІЯ: патерни тільки з ASCII-символів, тому string.gsub безпечний
local function clean_wikilinks(text)
    if not text then return nil end
    text = string.gsub(text, "%[%[([^%]|]+)|([^%]]+)%]%]", "%2")
    text = string.gsub(text, "%[%[([^%]]+)%]%]", "%1")
    return mw.text.trim(text)
end
local function strip_html_tags(text)
    if not text then return text end
    text = mw.ustring.gsub(text, "<span[^>]*>", "")
    text = mw.ustring.gsub(text, "</span>", "")
    return mw.text.trim(text)
end
end


Рядок 70: Рядок 87:
local function parse_row_cells(row)
local function parse_row_cells(row)
     local cells = {}
     local cells = {}
    row = mw.ustring.gsub(row, "^%s*|%s*", "")
 
   
     for line in mw.ustring.gmatch(row, "[^\n]+") do
     for cell in mw.ustring.gmatch(row, "|([^|]+)") do
         line = mw.text.trim(line)
         local trimmed = mw.text.trim(cell)
 
         if trimmed ~= "" then
         if line ~= "|-" and line ~= "" then
             table.insert(cells, trimmed)
             line = mw.ustring.gsub(line, "^|%s*", "")
 
            if line ~= "" then
                table.insert(cells, line)
            end
         end
         end
     end
     end
   
 
     return cells
     return cells
end
end


local function find_player_data(table_content, player_name)
local function find_player_in_table(table_content, player_name, column_index)
     if not table_content then return nil end
     if not table_content then return nil end
   
 
     local escaped = mw.ustring.gsub(player_name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
     local escaped = mw.ustring.gsub(player_name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
     local player_pattern = "%[%[" .. escaped .. "%]%]"
     local player_pattern = "%[%[" .. escaped .. "%]%]"
   
 
     local rows = parse_table_rows(table_content)
     local rows = parse_table_rows(table_content)
   
 
     for _, row in ipairs(rows) do
     for _, row in ipairs(rows) do
         if mw.ustring.find(row, player_pattern) then
         if mw.ustring.find(row, player_pattern) then
             return parse_row_cells(row)
             local cells = parse_row_cells(row)
            if column_index and column_index <= #cells then
                return cells[column_index]
            end
            return cells
         end
         end
     end
     end
   
 
     return nil
     return nil
end
-- Конвертація часу (якщо потрібно)
local function format_time(time_str)
    if not time_str or time_str == "" then return "" end
   
    time_str = mw.text.trim(time_str)
   
    -- Якщо вже в правильному форматі
    if mw.ustring.find(time_str, "хв") then
        return time_str
    end
   
    -- Парсимо формат HH:MM:SS або MM:SS
    local hours, minutes, seconds = mw.ustring.match(time_str, "(%d+):(%d+):(%d+)")
   
    if not hours then
        minutes, seconds = mw.ustring.match(time_str, "(%d+):(%d+)")
        hours = "0"
    end
   
    if minutes and seconds then
        hours = tonumber(hours) or 0
        minutes = tonumber(minutes) or 0
        seconds = tonumber(seconds) or 0
       
        if hours > 0 then
            return string.format("%d год %02d хв %02d с", hours, minutes, seconds)
        else
            return string.format("%d хв %02d с", minutes, seconds)
        end
    end
   
    return time_str
end
end


-- ================================================
-- ================================================
-- ФУНКЦІЇ З FETCHDATA
-- SEASON RESULT
-- ================================================
-- ================================================


function p.season_result(frame)
function p.season_result(frame)
     local player_name = frame.args.player or frame.args[1]
     local season = frame.args.season
     local season_number = frame.args.season or frame.args[2]
     local player = frame.args.player
   
 
    if not player_name or player_name == "" then
     local season_titles = {
        return error_output("Не вказано ім'я гравця")
         "Перший сезон", "Другий сезон", "Третій сезон",
    end
         "Четвертий сезон", "П'ятий сезон", "Шостий сезон",
   
         "Сьомий сезон", "Восьмий сезон", "Дев'ятий сезон"
    if not season_number or season_number == "" then
        return error_output("Не вказано номер сезону")
    end
   
     local season_page = nil
    local season_names = {
         ["1"] = "Перший сезон",
        ["2"] = "Другий сезон",
        ["3"] = "Третій сезон",
         ["4"] = "Четвертий сезон",
        ["5"] = "П'ятий сезон",
        ["6"] = "Шостий сезон",
         ["7"] = "Сьомий сезон",
        ["8"] = "Восьмий сезон",
        ["9"] = "Дев'ятий сезон"
     }
     }
   
 
     season_page = season_names[season_number]
     local season_title = season_titles[tonumber(season)]
   
     if not season_title then return "??" end
     if not season_page then
 
        return error_output("Невірний номер сезону")
     local content = get_page_content(season_title)
    end
     if not content then return "??" end
   
 
     local content = get_page_content(season_page)
     local rating_section = mw.ustring.match(content, "==%s*Рейтинг%s*==.-{|%s*class%s*=%s*\"wikitable sortable\"(.-)|}")
     if not content then
     if not rating_section then return "--" end
        return error_output("Не вдалося завантажити сторінку сезону")
 
    end
     local pattern = "|%s*(%d+)%s*|%s*%[%[" .. mw.ustring.gsub(player, "([%(%)%.%-%+%[%]])", "%%%1") .. "%]%]"
   
     local direct_pattern = "|%s*(%d+)%s*|%s*" .. mw.ustring.gsub(player, "([%(%)%.%-%+%[%]])", "%%%1") .. "%s*"
     local final_section = mw.ustring.match(content, "==+%s*Фінал%s*==+(.-)\n==")
 
    if not final_section then
     local rank = mw.ustring.match(rating_section, pattern) or mw.ustring.match(rating_section, direct_pattern)
        final_section = mw.ustring.match(content, "==+%s*Фінал%s*==+(.*)")
 
    end
     return rank or "--"
   
     if not final_section then
        return error_output("Не знайдено секцію Фінал")
    end
   
     local table_start = mw.ustring.find(final_section, "{|")
     local table_end = mw.ustring.find(final_section, "|}", table_start)
   
    if not table_start or not table_end then
        return error_output("Не знайдено таблицю у секції Фінал")
    end
   
     local table_content = mw.ustring.sub(final_section, table_start, table_end + 1)
    local player_data = find_player_data(table_content, player_name)
   
     if not player_data or #player_data < 2 then
        return "—"
    end
   
    local place = player_data[1]
    return place or ""
end
end


-- ================================================
-- ================================================
-- ФУНКЦІЇ З FETCHDATA2
-- БАЗОВА СТАТИСТИКА
-- ================================================
-- ================================================


function p.games_count(frame)
function p.games_count(frame)
     local player_name = frame.args.player or frame.args[1]
     local player_name = frame.args.player
   
     if not player_name or player_name == "" then return "0" end
     if not player_name or player_name == "" then
 
        return error_output("Не вказано ім'я гравця")
     local result = find_player_in_table(get_table_from_page("Статистика"), player_name, 2)
    end
     return result or "0"
   
     local table_content = get_table_from_page("Статистика")
    if not table_content then
        return error_output("Не вдалося завантажити таблицю")
    end
   
    local data = find_player_data(table_content, player_name)
   
    if not data or #data < 3 then
        return "0"
    end
   
     return data[3] or "0"
end
end


function p.wins_count(frame)
function p.wins_count(frame)
     local player_name = frame.args.player or frame.args[1]
     local player_name = frame.args.player
   
     if not player_name or player_name == "" then return "0" end
     if not player_name or player_name == "" then
 
        return error_output("Не вказано ім'я гравця")
     local result = find_player_in_table(get_table_from_page("Статистика"), player_name, 3)
    end
     return result or "0"
   
     local table_content = get_table_from_page("Статистика")
    if not table_content then
        return error_output("Не вдалося завантажити таблицю")
    end
   
    local data = find_player_data(table_content, player_name)
   
    if not data or #data < 4 then
        return "0"
    end
   
     return data[4] or "0"
end
end


function p.losses_count(frame)
function p.losses_count(frame)
     local player_name = frame.args.player or frame.args[1]
     local player_name = frame.args.player
   
     if not player_name or player_name == "" then return "0" end
     if not player_name or player_name == "" then
 
        return error_output("Не вказано ім'я гравця")
     local result = find_player_in_table(get_table_from_page("Статистика"), player_name, 4)
    end
     return result or "0"
   
     local table_content = get_table_from_page("Статистика")
    if not table_content then
        return error_output("Не вдалося завантажити таблицю")
    end
   
    local data = find_player_data(table_content, player_name)
   
    if not data or #data < 5 then
        return "0"
    end
   
     return data[5] or "0"
end
end


function p.win_rate_colored(frame)
function p.win_rate_colored(frame)
     local player_name = frame.args.player or frame.args[1]
     local player_name = frame.args.player
   
     if not player_name or player_name == "" then return "0%" end
     if not player_name or player_name == "" then
 
        return error_output("Не вказано ім'я гравця")
    local win_rate = find_player_in_table(get_table_from_page("Статистика"), player_name, 5)
    if not win_rate then return "0%" end
 
    local rate_num = tonumber(mw.ustring.match(win_rate, "([%d%.]+)"))
    if not rate_num then return win_rate end
 
    local color = "#4caf50"
    if rate_num < 40 then
        color = "indianred"
    elseif rate_num < 50 then
        color = "orange"
     end
     end
   
 
     local table_content = get_table_from_page("Статистика")
     if not mw.ustring.find(win_rate, "%%") then
    if not table_content then
         win_rate = win_rate .. "%"
         return error_output("Не вдалося завантажити таблицю")
     end
     end
   
 
     local data = find_player_data(table_content, player_name)
     return string.format('<span style="color:%s;">%s</span>', color, win_rate)
   
    if not data or #data < 6 then
        return '<span style="color:indianred;">0%</span>'
    end
   
    local winrate = data[6]
    winrate = mw.ustring.gsub(winrate, "%%", "")
    winrate = mw.text.trim(winrate)
   
    local rate = tonumber(winrate) or 0
    local color = rate < 40 and "indianred" or "#4caf50"
   
    return '<span style="color:' .. color .. ';">' .. winrate .. '%</span>'
end
end


function p.recruiter(frame)
function p.recruiter(frame)
     local player_name = frame.args.player or frame.args[1]
     local player_name = frame.args.player
   
     if not player_name or player_name == "" then return "Не вказано" end
     if not player_name or player_name == "" then
 
        return error_output("Не вказано ім'я гравця")
    end
   
     local table_content = get_table_from_page("Гравці")
     local table_content = get_table_from_page("Гравці")
     if not table_content then
     if not table_content then return "Не вказано" end
        return error_output("Не вдалося завантажити таблицю")
 
    end
     local cells = find_player_in_table(table_content, player_name)
   
     if not cells or #cells < 2 then return "Не вказано" end
     local data = find_player_data(table_content, player_name)
 
   
    local raw = mw.text.trim(cells[2])
     if not data or #data < 4 then
    if raw == "Відсутній" or raw == "-" or raw == "" then
         return "Не вказано"
         return "Не вказано"
     end
     end
   
 
    local recruiter = mw.text.trim(data[4])
     return raw
   
    if recruiter == "Відсутній" or recruiter == "-" or recruiter == "" then
        return "Не вказано"
    end
   
     return recruiter
end
end


function p.date_added(frame)
function p.date_added(frame)
     local player_name = frame.args.player or frame.args[1]
     local player_name = frame.args.player
   
     if not player_name or player_name == "" then return "Лише Бог знає" end
     if not player_name or player_name == "" then
 
        return error_output("Не вказано ім'я гравця")
     local raw = find_player_in_table(get_table_from_page("Гравці"), player_name, 3)
    end
 
   
     if not raw or raw == "" or raw == "-" or raw == "Відсутній" then
     local table_content = get_table_from_page("Гравці")
    if not table_content then
        return error_output("Не вдалося завантажити таблицю")
    end
   
    local data = find_player_data(table_content, player_name)
   
     if not data or #data < 3 then
         return "Лише Бог знає"
         return "Лише Бог знає"
     end
     end
   
 
     local raw_date = data[3]
     local day, month, year = mw.ustring.match(raw, "(%d+)%.(%d+)%.(%d+)")
   
 
     if not raw_date or raw_date == "" or raw_date == "-" then
     if day and month and year then
         return "Лише Бог знає"
        local end_date = os.time({year=2024, month=12, day=1})
        local start_date = os.time({year=tonumber(year), month=tonumber(month), day=tonumber(day)})
        local days_diff = math.floor((end_date - start_date) / 86400)
 
         return string.format("%s (%d днів)", raw, days_diff)
     end
     end
   
 
    local day, month, year = mw.ustring.match(raw_date, "(%d+)%.(%d+)%.(%d+)")
     return "Лише Бог знає"
   
    if not day or not month or not year then
        return raw_date
    end
   
    local end_date = os.time({year=2024, month=10, day=26})
    local start_date = os.time({year=tonumber(year), month=tonumber(month), day=tonumber(day)})
    local days_diff = math.floor((end_date - start_date) / 86400)
   
     return string.format("%s (%d днів)", raw_date, days_diff)
end
end


function p.foundation(frame)
function p.foundation(frame)
     local player_name = frame.args.player or frame.args[1]
     local player_name = frame.args.player
   
     if not player_name or player_name == "" then return "0₴" end
     if not player_name or player_name == "" then
 
        return error_output("Не вказано ім'я гравця")
     local raw = find_player_in_table(get_table_from_page("Фундація"), player_name, 2)
    end
 
   
     if not raw or raw == "" or raw == "-" then return "0₴" end
     local table_content = get_table_from_page("Фундація")
 
    if not table_content then
     raw = mw.ustring.gsub(raw, "[^%d%s]", "")
        return error_output("Не вдалося завантажити таблицю")
    raw = mw.text.trim(raw)
    end
 
   
     return (raw or "0") .. " ₴"
    local data = find_player_data(table_content, player_name)
   
     if not data or #data < 3 then
        return "0"
    end
   
     local amount = mw.ustring.gsub(data[3], "[^%d]", "")
     return amount
end
end


function p.prize_pool(frame)
function p.prize_pool(frame)
     local player_name = frame.args.player or frame.args[1]
     local player_name = frame.args.player
   
     if not player_name or player_name == "" then return "0₴" end
     if not player_name or player_name == "" then
 
        return error_output("Не вказано ім'я гравця")
     local raw = find_player_in_table(get_table_from_page("Призовий_фонд"), player_name, 2)
    end
 
   
     if not raw or raw == "" or raw == "-" then return "0₴" end
     local table_content = get_table_from_page("Призовий_фонд")
 
    if not table_content then
     raw = mw.ustring.gsub(raw, "[^%d%s]", "")
        return error_output("Не вдалося завантажити таблицю")
    raw = mw.text.trim(raw)
    end
 
   
     return (raw or "0") .. " ₴"
    local data = find_player_data(table_content, player_name)
   
     if not data or #data < 2 then
        return "0"
    end
   
     local amount = mw.ustring.gsub(data[2], "[^%d]", "")
     return amount
end
end


function p.finalist(frame)
function p.finalist(frame)
     local player_name = frame.args.player or frame.args[1]
     local player_name = frame.args.player
      
    if not player_name or player_name == "" then return "0/9" end
     if not player_name or player_name == "" then
 
         return error_output("Не вказано ім'я гравця")
     local raw = find_player_in_table(get_table_from_page("Фіналіст"), player_name, 2)
 
     if not raw or raw == "" or raw == "-" then return "0/9" end
 
    local number_only = mw.ustring.gsub(raw, "[^%d]", "")
    local count = tonumber(number_only)
 
    if count then
         return string.format("%d/9", count)
     end
     end
   
 
     local table_content = get_table_from_page("Фіналіст")
     return "0/9"
    if not table_content then
        return error_output("Не вдалося завантажити таблицю")
    end
   
    local data = find_player_data(table_content, player_name)
   
    if not data or #data < 3 then
        return "0"
    end
   
    local count = mw.ustring.gsub(data[3], "[^%d]", "")
    return count
end
end


function p.foty_rating(frame)
function p.foty_rating(frame)
     local player_name = frame.args.player or frame.args[1]
     local player_name = frame.args.player
   
     if not player_name or player_name == "" then return "" end
     if not player_name or player_name == "" then
 
        return error_output("Не вказано ім'я гравця")
    end
   
     local content = get_page_content("Період")
     local content = get_page_content("Період")
     if not content then
     if not content then return "—" end
        return "—"
 
    end
   
     local foty_section = mw.ustring.match(content, "==+%s*Фінал Року%s*==+(.-)\n==")
     local foty_section = mw.ustring.match(content, "==+%s*Фінал Року%s*==+(.-)\n==")
     if not foty_section then
     if not foty_section then
         foty_section = mw.ustring.match(content, "==+%s*Фінал Року%s*==+(.*)")
         foty_section = mw.ustring.match(content, "==+%s*Фінал Року%s*==+(.*)")
     end
     end
   
 
     if not foty_section then
     if not foty_section then return "—" end
        return "—"
 
    end
   
     local table_start = mw.ustring.find(foty_section, "{|")
     local table_start = mw.ustring.find(foty_section, "{|")
     local table_end = mw.ustring.find(foty_section, "|}", table_start)
     local table_end = mw.ustring.find(foty_section, "|}", table_start)
   
 
     if not table_start or not table_end then
     if not table_start or not table_end then return "—" end
        return "—"
 
    end
   
     local table_content = mw.ustring.sub(foty_section, table_start, table_end + 1)
     local table_content = mw.ustring.sub(foty_section, table_start, table_end + 1)
     local data = find_player_data(table_content, player_name)
     local cells = find_player_in_table(table_content, player_name)
   
 
     if not data or #data < 3 then
     if not cells or #cells < 3 then return "—" end
        return "—"
 
    end
     local place = strip_html_tags(cells[1]) or "—"
   
     local rating = strip_html_tags(cells[3]) or ""
     local place = mw.text.trim(data[1])
 
     local rating = mw.text.trim(data[3])
   
    rating = mw.ustring.gsub(rating, "<span[^>]*>", "")
    rating = mw.ustring.gsub(rating, "</span>", "")
     rating = mw.ustring.gsub(rating, "[^%d%-]", "")
     rating = mw.ustring.gsub(rating, "[^%d%-]", "")
     rating = mw.text.trim(rating)
     rating = mw.text.trim(rating)
   
 
     if rating and rating ~= "" then
     if rating and rating ~= "" then
         return rating .. " (" .. place .. " місце)"
         return rating .. " (" .. place .. " місце)"
     end
     end
   
 
     return "—"
     return "—"
end
end


-- ================================================
-- ================================================
-- ФУНКЦІЇ З FETCHDATA5 (player_games)
-- ТИТУЛИ ТА НАГОРОДИ
-- ================================================
-- ================================================


function p.player_games(frame)
local tournament_dates = {
     local player_name = frame.args.player or frame.args[1]
    ["Women's Closed Cup I"] = "15.03.2023",
      
    ["Men's Closed Cup I"] = "05.04.2023",
     if not player_name or player_name == "" then
    ["Combined Closed Cup I"] = "21.04.2023",
        return error_output("Не вказано ім'я гравця")
    ["Score Open Battle I"] = "07.05.2023",
    ["Women's Closed Cup II"] = "11.05.2023",
    ["Men's Closed Cup II"] = "27.05.2023",
    ["Combined Closed Cup II"] = "02.06.2023",
    ["Score Open Battle II"] = "17.06.2023",
    ["Mafia Closed Cup I"] = "12.11.2023",
    ["Mafia Closed Cup I Online"] = "28.01.2024",
    ["My Closest Circle I"] = "01.01.2023",
    ["Get Names 01"] = "08.10.2023",
    ["Get Names 02"] = "03.12.2023",
    ["Get Names 03"] = "10.03.2024",
    ["Get Names 04"] = "27.04.2024",
    ["Get Names 05"] = "14.07.2024",
    ["Get Names 06"] = "21.07.2024",
    ["Get Names 07"] = "08.09.2024",
    ["Get Names 08"] = "19.09.2024",
    ["Get Names 09"] = "29.09.2024",
    ["Перший сезон (фінал)"] = "09.09.2023",
    ["Перший сезон (рейтинг)"] = "09.09.2023",
    ["Другий сезон (фінал)"] = "28.10.2023",
    ["Другий сезон (рейтинг)"] = "28.10.2023",
    ["Третій сезон (фінал)"] = "23.12.2023",
    ["Третій сезон (рейтинг)"] = "23.12.2023",
    ["Четвертий сезон (фінал)"] = "10.02.2024",
    ["Четвертий сезон (рейтинг)"] = "10.02.2024",
    ["П'ятий сезон (фінал)"] = "06.04.2024",
    ["П'ятий сезон (рейтинг)"] = "06.04.2024",
    ["Шостий сезон (фінал)"] = "11.05.2024",
    ["Шостий сезон (рейтинг)"] = "11.05.2024",
    ["Сьомий сезон (фінал)"] = "06.07.2024",
    ["Сьомий сезон (рейтинг)"] = "06.07.2024",
    ["Восьмий сезон (фінал)"] = "10.08.2024",
    ["Восьмий сезон (рейтинг)"] = "10.08.2024",
    ["Дев'ятий сезон (фінал)"] = "28.09.2024",
    ["Дев'ятий сезон (рейтинг)"] = "28.09.2024",
    ["Фінал Року"] = "13.10.2024"
}
 
local function get_medal_icon(value)
    if not value or value == "" or value == " " then return nil end
    value = mw.text.trim(value)
 
    if value == "" then return nil
    elseif value == "1" then return "[[Файл:Gold.png|20px|link=]]"
    elseif value == "2" then return "[[Файл:Silver.png|20px|link=]]"
    elseif value == "3" then return "[[Файл:Bronze.png|20px|link=]]"
    elseif value == "4" then return "[[Файл:Finalist.png|20px|link=]]"
    elseif value == "S" or mw.ustring.match(value, "^S%d") then return "[[Файл:Star.png|20px|link=]]"
    else return nil
    end
end
 
local function to_roman(num)
    local romans = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"}
    return romans[tonumber(num)] or num
end
 
local function get_tournament_link(header_name)
    local season_num = mw.ustring.match(header_name, "([А-Яа-яЁёІіЇїЄєҐґ']+) сезон %(фінал%)")
    if not season_num then
        season_num = mw.ustring.match(header_name, "([А-Яа-яЁёІіЇїЄєҐґ']+) сезон %(рейтинг%)")
    end
 
    if season_num then
        local season_map = {
            ["Перший"] = 1, ["Другий"] = 2, ["Третій"] = 3,
            ["Четвертий"] = 4, ["П'ятий"] = 5, ["Шостий"] = 6,
            ["Сьомий"] = 7, ["Восьмий"] = 8, ["Дев'ятий"] = 9
        }
 
        local season_number = season_map[season_num]
        if season_number then
            local roman = to_roman(season_number)
            local type_text = mw.ustring.find(header_name, "фінал") and "Фінал." or "Рейтинг."
            local clean_name = mw.ustring.gsub(header_name, "%s*%(фінал%)%s*", "")
            clean_name = mw.ustring.gsub(clean_name, "%s*%(рейтинг%)%s*", "")
            clean_name = mw.text.trim(clean_name)
 
            return string.format("[[%s|%s сезон. %s]]", clean_name, roman, type_text)
        end
    end
 
    return string.format("[[%s]]", header_name)
end
 
local function get_player_row(page_title, player_name)
    local table_content = get_table_from_page(page_title)
    if not table_content then return nil end
 
    local first_row = mw.ustring.match(table_content, "{|[^\n]*\n!(.-)[\n]")
    local headers = {}
 
    if first_row then
        for header in mw.ustring.gmatch(first_row, "([^|]+)") do
            local trimmed = mw.text.trim(header)
            if trimmed ~= "" then
                table.insert(headers, trimmed)
            end
        end
    end
 
    if #headers == 0 then return nil end
 
    local cells = find_player_in_table(table_content, player_name)
    if not cells then return nil end
 
    return {headers = headers, cells = cells}
end
 
function p.player_image(frame)
     local nickname = frame.args.nickname or frame.args[1] or mw.title.getCurrentTitle().text
     local player_file = "Player_" .. nickname .. ".png"
     local placeholder = "MCC_Placeholder.png"
 
    local title = mw.title.new("File:" .. player_file)
 
    local file_to_use
    if title and title.exists then
        file_to_use = player_file
    else
        file_to_use = placeholder
     end
     end
      
 
     local page_content = get_page_content("Записи_ігор")
     return string.format("[[File:%s|none|alt=Фотографія гравця|center|360px]]", file_to_use)
     if not page_content then
end
        return error_output("Не вдалося завантажити сторінку Записи_ігор")
 
function p.titles(frame)
     local name = frame.args.player
    if not name or name == "" then return "''Відсутні''" end
 
    local titles_data = get_player_row("Титули", name)
     if not titles_data then return "''Відсутні''" end
 
    local prize_data = get_player_row("Призові", name)
    if not prize_data then return error_output("Prize Data Missing", name) end
 
    local results = {}
 
    for i = 2, #titles_data.cells do
        local cell_value = titles_data.cells[i]
        local header = titles_data.headers[i]
        local prize_value = prize_data.cells[i]
 
        if cell_value and cell_value ~= "" and cell_value ~= "-" and header then
            local medal = get_medal_icon(cell_value)
 
            if medal then
                local prize_amount = "0 ₴"
 
                if prize_value and prize_value ~= "" and prize_value ~= "-" then
                    prize_value = mw.ustring.gsub(prize_value, "₴", "")
                    prize_value = mw.text.trim(prize_value)
                    if prize_value ~= "0" then
                        prize_amount = prize_value .. " ₴"
                    end
                end
 
                local date = tournament_dates[header] or "01.01.2023"
                local tournament_link = get_tournament_link(header)
 
                table.insert(results, {
                    date = date,
                    medal = medal,
                    tournament = tournament_link,
                    prize = prize_amount
                })
            end
        end
     end
     end
      
 
     local table_start = mw.ustring.find(page_content, "{|")
     if #results == 0 then return "''Відсутні''" end
    local table_end = mw.ustring.find(page_content, "|}", table_start)
 
      
     local table_html = {
     if not table_start or not table_end then
        '{| class="wikitable sortable" style="font-size: 14.5px;"',
         return error_output("Не знайдено таблицю")
        '|-',
        '! style="" | Дата',
        '! style="text-align: left;" | Місце і турнір',
        '! style="text-align: left;" | Приз'
     }
 
     for _, result in ipairs(results) do
        table.insert(table_html, '|-')
         table.insert(table_html, string.format(
            '| style="width:80px; text-align:center;" | %s || style="text-align:left; padding-left:10px;" | %s %s || style="text-align:right;" | %s',
            result.date, result.medal, result.tournament, result.prize
        ))
     end
     end
      
 
     local table_content = mw.ustring.sub(page_content, table_start, table_end + 1)
     table.insert(table_html, '|}')
     local rows = parse_table_rows(table_content)
     return table.concat(table_html, '\n')
      
end
     if #rows < 1 then
 
         return error_output("Таблиця порожня")
-- ================================================
-- ЦІКАВІ ФАКТИ
-- ================================================
 
local pages_to_search = {
    "Перший сезон", "Другий сезон", "Третій сезон", "Четвертий сезон",
    "П'ятий сезон", "Шостий сезон", "Сьомий сезон", "Восьмий сезон",
    "Дев'ятий сезон", "Mafia Closed Circle", "Ten's Games", "Alternatywa",
    "Майстерня Стратегічного Саморозвитку", "Get Names 01", "Get Names 02",
    "Get Names 03", "Get Names 04", "Get Names 05", "Get Names 06",
    "Get Names 07", "Get Names 08", "Get Names 09", "Women's Closed Cup I",
    "Women's Closed Cup II", "Men's Closed Cup I", "Men's Closed Cup II",
    "Combined Closed Cup I", "Combined Closed Cup II", "Score Open Battle I",
    "Score Open Battle II", "Mafia Closed Cup I", "Mafia Closed Cup I Online",
    "My Closest Circle I", "Фінал Року"
}
 
local function get_facts_section(page_title)
    local content = get_page_content(page_title)
    if not content then return nil end
 
    local section_start = mw.ustring.find(content, "==%s*[Цц]ікаві%s*[Фф]акти%s*==")
    if not section_start then return nil end
 
     local section_end = mw.ustring.find(content, "\n==[^=]", section_start + 1)
 
     local section_content
     if section_end then
        section_content = mw.ustring.sub(content, section_start, section_end - 1)
    else
         section_content = mw.ustring.sub(content, section_start)
     end
     end
      
 
     return section_content
end
 
local function fact_contains_player(fact, player_name)
     local escaped_name = mw.ustring.gsub(player_name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
     local escaped_name = mw.ustring.gsub(player_name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
     local player_pattern = "%[%[" .. escaped_name .. "%]%]"
     local pattern1 = "%[%[" .. escaped_name .. "%]%]"
      
     local pattern2 = "%[%[" .. escaped_name .. "|[^%]]+%]%]"
     local player_games = {}
 
      
    return mw.ustring.find(fact, pattern1) or mw.ustring.find(fact, pattern2)
     for _, row in ipairs(rows) do
end
         if mw.ustring.find(row, player_pattern) then
 
             local cells = parse_row_cells(row)
local function extract_facts(section_content)
           
     local facts = {}
     for fact in mw.ustring.gmatch(section_content, "%*[^\n]+") do
        table.insert(facts, fact)
     end
    return facts
end
 
function p.facts(frame)
    local player_name = frame.args.player or frame.args[1] or mw.title.getCurrentTitle().text
 
    local result_parts = {}
 
    for _, page_title in ipairs(pages_to_search) do
        local section = get_facts_section(page_title)
 
        if section then
            local facts = extract_facts(section)
            local player_facts = {}
 
            for _, fact in ipairs(facts) do
                if fact_contains_player(fact, player_name) then
                    table.insert(player_facts, fact)
                end
            end
 
            if #player_facts > 0 then
                local block = string.format("==== %s ====\n", page_title)
                for _, fact in ipairs(player_facts) do
                    block = block .. fact .. "\n"
                end
                table.insert(result_parts, string.format('<div style="margin-bottom: 20px;">\n%s</div>', block))
            end
         end
    end
 
    if #result_parts == 0 then return "" end
 
    return table.concat(result_parts, "\n")
end
 
-- ================================================
-- ЗАПИСИ ІГОР
-- ================================================
 
local function get_tournament_link_games(short_name, full_name)
    if not short_name or short_name == "" then
        return "Невідомо"
    end
 
    if short_name == "Фанова гра" then
        return "Фанова гра"
    end
 
    if full_name then
        full_name = clean_wikilinks(full_name)
    end
 
    if full_name and full_name ~= "" and full_name ~= short_name then
        return "[[" .. full_name .. "|" .. short_name .. "]]"
    else
        return "[[" .. short_name .. "]]"
    end
end
 
local function parse_games_row_cells(row)
    local cells = {}
    row = string.gsub(row, "^%s*|%s*", "")
    for cell in string.gmatch(row .. "||", "(.-)%s*||%s*") do
        table.insert(cells, mw.text.trim(cell))
    end
    return cells
end
 
-- Кеш: таблиця розбирається ОДИН раз на сторінку
local games_rows_cache = nil
local player_games_cache = {}
 
local function get_games_rows()
    if games_rows_cache then return games_rows_cache end
 
    local table_content = get_table_from_page("Ігри")
    if not table_content then return nil end
 
    local rows = {}
    for row in string.gmatch(table_content, "|-\n([^\n]+)") do
        if not string.match(row, "^%s*!") then
             local cells = parse_games_row_cells(row)
             if #cells >= 17 then
             if #cells >= 17 then
                 local game_type = cells[2]
                 local names = {}
                local tournament = cells[4]
                 for i = 6, 15 do
                local time = cells[5]
                     names[i] = clean_wikilinks(cells[i])
                local result = cells[16]
                local link = cells[17]
               
                local position = nil
                local role = nil
               
                 for j = 6, 15 do
                     local cell_player = clean_wikilinks(cells[j])
                    if cell_player == player_name then
                        position = j - 5
                       
                        if j >= 6 and j <= 11 then
                            role = "Мирний"
                        elseif j == 12 then
                            role = "Шериф"
                        elseif j >= 13 and j <= 14 then
                            role = "Мафія"
                        elseif j == 15 then
                            role = "Дон"
                        end
                        break
                    end
                end
               
                if position and role then
                    table.insert(player_games, {
                        type = game_type,
                        tournament = tournament,
                        time = time,
                        position = position,
                        role = role,
                        result = result,
                        link = link
                    })
                 end
                 end
                table.insert(rows, { cells = cells, names = names })
             end
             end
         end
         end
     end
     end
      
 
     if #player_games == 0 then
    games_rows_cache = rows
         return "''Записів ігор не знайдено''"
    return rows
end
 
local function has_unknown_roles(cells)
     local res = cells[16]
     if not res or mw.text.trim(res) == "" then return true end
    return string.find(res, "?", 1, true) ~= nil
end
 
local function get_player_role(player_name, entry)
    if has_unknown_roles(entry.cells) then return "—" end
 
    local names = entry.names
    for i = 6, 11 do
         if names[i] == player_name then return "Мир" end
    end
    if names[12] == player_name then return "Шер" end
    for i = 13, 14 do
        if names[i] == player_name then return "Маф" end
    end
    if names[15] == player_name then return "Дон" end
 
    return "—"
end
 
local function get_player_result(role, game_result)
    if not game_result or game_result == "" then return "—" end
 
    game_result = mw.text.trim(game_result)
 
    if string.find(game_result, "?", 1, true) then return "—" end
 
    if game_result == "Місто" or game_result == "Мирні" then
        return (role == "Мир" or role == "Шер") and "Перемога" or "Поразка"
    end
 
    if game_result == "Мафія" then
        return (role == "Маф" or role == "Дон") and "Перемога" or "Поразка"
    end
 
    return "—"
end
 
local function get_player_games_list(player_name)
    if player_games_cache[player_name] then
        return player_games_cache[player_name]
     end
     end
   
 
     local output = {}
     local rows = get_games_rows()
    table.insert(output, '{| class="wikitable sortable" style="font-size: 14px;"')
     if not rows then return nil end
     table.insert(output, '|-')
 
    table.insert(output, '! №')
     local games = {}
    table.insert(output, '! Тип')
 
     table.insert(output, '! Турнір')
     for _, entry in ipairs(rows) do
    table.insert(output, '! Час')
         local found = false
    table.insert(output, '! Позиція')
         for i = 6, 15 do
    table.insert(output, '! Роль')
            if entry.names[i] == player_name then
    table.insert(output, '! Результат')
                found = true
    table.insert(output, '! Відео')
                break
   
             end
     for i, game in ipairs(player_games) do
         table.insert(output, '|-')
         table.insert(output, '| style="text-align:center;" | ' .. i)
        table.insert(output, '| ' .. game.type)
        table.insert(output, '| ' .. game.tournament)
        table.insert(output, '| style="text-align:center;" | ' .. game.time)
        table.insert(output, '| style="text-align:center;" | ' .. game.position)
       
        local role_color = ""
        if game.role == "Мирний" then
            role_color = "#90EE90"
        elseif game.role == "Шериф" then
            role_color = "#4169E1"
        elseif game.role == "Мафія" then
             role_color = "#DC143C"
        elseif game.role == "Дон" then
            role_color = "#8B0000"
         end
         end
          
 
        table.insert(output, '| style="background-color:' .. role_color .. '; text-align:center;" | ' .. game.role)
         if found then
       
            local cells = entry.cells
        local result_style = ""
            local role = get_player_role(player_name, entry)
        if game.result == "Місто" or game.result == "Мирні" then
            local result = get_player_result(role, cells[16])
            result_style = 'style="color:#4caf50; font-weight:bold;"'
 
        elseif game.result == "Мафія" then
            local time_raw = cells[5] or ""
             result_style = 'style="color:#f44336; font-weight:bold;"'
            local time_formatted = time_raw
            local minutes, seconds = string.match(time_raw, "(%d+)%s*хв%s*(%d+)%s*с")
            if minutes and seconds then
                time_formatted = minutes .. ":" .. string.format("%02d", tonumber(seconds))
            end
 
            table.insert(games, {
                short = cells[3] or "",
                tournament = cells[4] or "",
                time = time_formatted,
                role = role,
                result = result,
                link = cells[17] or ""
             })
         end
         end
       
        table.insert(output, '| ' .. result_style .. ' | ' .. game.result)
        table.insert(output, '| [' .. game.link .. ' YouTube]')
     end
     end
   
 
    table.insert(output, '|}')
     player_games_cache[player_name] = games
      
     return games
     return table.concat(output, '\n')
end
end


function p.games_count_records(frame)
function p.player_games(frame)
     local player_name = frame.args.player or frame.args[1]
     local player_name = frame.args.player
   
     if not player_name or player_name == "" then
     if not player_name or player_name == "" then
         return "0"
         return error_output("No Player Name", "")
     end
     end
   
 
     local page_content = get_page_content("Записи_ігор")
     local games = get_player_games_list(player_name)
     if not page_content then
     if not games then
         return "0"
         return error_output("Cannot Load Games", "Ігри")
     end
     end
   
 
     local table_start = mw.ustring.find(page_content, "{|")
     if #games == 0 then
    local table_end = mw.ustring.find(page_content, "|}", table_start)
         return "''Ігор не знайдено''"
   
    if not table_start or not table_end then
         return "0"
     end
     end
   
 
     local table_content = mw.ustring.sub(page_content, table_start, table_end + 1)
     local htmlTable = mw.html.create('table')
     local rows = parse_table_rows(table_content)
        :addClass('wikitable sortable')
      
        :css('width', '100%')
     local escaped_name = mw.ustring.gsub(player_name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
        :css('font-size', '14.5px')
    local player_pattern = "%[%[" .. escaped_name .. "%]%]"
 
   
     local headerRow = htmlTable:tag('tr')
    local count = 0
    headerRow:tag('th'):wikitext('Подія')
    for _, row in ipairs(rows) do
     headerRow:tag('th'):wikitext('Роль')
         if mw.ustring.find(row, player_pattern) then
     headerRow:tag('th'):wikitext('Час')
             count = count + 1
    headerRow:tag('th'):wikitext('🏆')
    headerRow:tag('th'):wikitext('Запис')
 
    for _, game in ipairs(games) do
        local row = htmlTable:tag('tr')
        local unknown = (game.result == "—")
 
        -- Турнір
        row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
            :wikitext(get_tournament_link_games(game.short, game.tournament))
 
        -- Роль
        row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
            :tag('span')
                :css('color', unknown and '#777' or '#fff')
                :wikitext(game.role)
 
        -- Час
        row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
            :wikitext(game.time)
 
        -- Результат
        local resultCell = row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
 
        if game.result == "Перемога" then
            resultCell:tag('span')
                :css('color', '#4caf50')
                :wikitext('В')
        elseif game.result == "Поразка" then
            resultCell:tag('span')
                :css('color', 'indianred')
                :wikitext('П')
        else
            resultCell:tag('span')
                :css('color', '#777')
                :wikitext('—')
        end
 
        -- Запис
        local linkCell = row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
 
         if game.link and game.link ~= "" then
             linkCell:wikitext('<span class="game-record-btn">[' .. game.link .. ' ▶︎]</span>')
         end
         end
     end
     end
      
 
     return tostring(count)
    return tostring(htmlTable)
end
 
function p.games_count_records(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0" end
 
     local games = get_player_games_list(player_name)
     return games and tostring(#games) or "0"
end
end


-- ================================================
-- ================================================
-- ФУНКЦІЇ З FETCHDATA6 (season_achievements)
-- ДОСЯГНЕННЯ В СЕЗОНАХ
-- ================================================
-- ================================================


function p.season_achievements(frame)
local function get_season_data(season_number, player_name)
    local player_name = frame.args.player or frame.args[1]
   
    if not player_name or player_name == "" then
        return error_output("Не вказано ім'я гравця")
    end
   
    local season_data = {}
     local season_names = {
     local season_names = {
         "Перший сезон",
         [1] = "Перший сезон", [2] = "Другий сезон", [3] = "Третій сезон",
        "Другий сезон",
         [4] = "Четвертий сезон", [5] = "П'ятий сезон", [6] = "Шостий сезон",
        "Третій сезон",
         [7] = "Сьомий сезон", [8] = "Восьмий сезон", [9] = "Дев'ятий сезон"
         "Четвертий сезон",
        "П'ятий сезон",
        "Шостий сезон",
         "Сьомий сезон",
        "Восьмий сезон",
        "Дев'ятий сезон"
     }
     }
      
 
     for i, season_name in ipairs(season_names) do
     local season_name = season_names[season_number]
        local content = get_page_content(season_name)
     if not season_name then return nil end
       
 
        if content then
    local content = get_page_content(season_name)
            local final_section = mw.ustring.match(content, "==+%s*Фінал%s*==+(.-)\n==")
    if not content then return nil end
            if not final_section then
 
                final_section = mw.ustring.match(content, "==+%s*Фінал%s*==+(.*)")
    local rating_section = mw.ustring.match(content, "==+%s*Рейтинг%s*==+(.-)\n==")
    if not rating_section then
        rating_section = mw.ustring.match(content, "==+%s*Рейтинг%s*==+(.*)")
    end
    if not rating_section then return nil end
 
    local table_start = mw.ustring.find(rating_section, "{|")
    local table_end = mw.ustring.find(rating_section, "|}", table_start)
    if not table_start or not table_end then return nil end
 
    local table_content = mw.ustring.sub(rating_section, table_start, table_end + 1)
 
    local current_row = {}
    local all_rows = {}
 
    for line in mw.ustring.gmatch(table_content .. "\n", "([^\n]*)\n") do
        line = mw.text.trim(line)
 
        if line == "|-" then
            if #current_row > 0 then
                table.insert(all_rows, current_row)
             end
             end
              
             current_row = {}
            if final_section then
        elseif mw.ustring.match(line, "^|[^-}]") then
                local table_start = mw.ustring.find(final_section, "{|")
            local cell_content = mw.ustring.gsub(line, "^|%s*", "")
                local table_end = mw.ustring.find(final_section, "|}", table_start)
            table.insert(current_row, cell_content)
               
        end
                if table_start and table_end then
    end
                    local table_content = mw.ustring.sub(final_section, table_start, table_end + 1)
 
                    local player_data = find_player_data(table_content, player_name)
    if #current_row > 0 then
                   
        table.insert(all_rows, current_row)
                    if player_data and #player_data >= 1 then
    end
                        table.insert(season_data, {
 
                            season = i,
    for _, row in ipairs(all_rows) do
                            place = player_data[1] or "—"
        if #row >= 5 then
                        })
            local player_cell = clean_wikilinks(row[2])
                     end
 
                 end
            if player_cell == player_name then
                return {
                    place = mw.text.trim(row[1]),
                    points = mw.text.trim(row[3]),
                    games = mw.text.trim(row[4]),
                     winrate = mw.text.trim(row[5])
                 }
             end
             end
         end
         end
     end
     end
      
 
     if #season_data == 0 then
    return nil
         return "''Гравець не брав участі в жодному сезоні''"
end
 
function p.season_achievements(frame)
     local player_name = frame.args.player
     if not player_name or player_name == "" then
         return "<span style='color:red;'>Ім'я гравця не вказано</span>"
     end
     end
      
 
     local output = {}
     local htmlTable = mw.html.create('table')
     table.insert(output, '{| class="wikitable" style="text-align:center; font-size: 14px;"')
        :addClass('wikitable sortable')
    table.insert(output, '|-')
        :css('width', '100%')
      
        :css('font-size', '14.5px')
     for _, data in ipairs(season_data) do
 
         table.insert(output, '! [[' .. season_names[data.season] .. '|' .. data.season .. ']]')
     local headerRow = htmlTable:tag('tr')
    headerRow:tag('th'):wikitext('Сезон')
    headerRow:tag('th'):wikitext('Місце')
    headerRow:tag('th'):wikitext('Бали')
    headerRow:tag('th'):wikitext('% перемог')
     headerRow:tag('th'):wikitext('Ігор')
 
    local season_links = {
        [1] = "[[Перший сезон#Рейтинг|01 сезон]]",
        [2] = "[[Другий сезон#Рейтинг|02 сезон]]",
        [3] = "[[Третій сезон#Рейтинг|03 сезон]]",
        [4] = "[[Четвертий сезон#Рейтинг|04 сезон]]",
        [5] = "[[П'ятий сезон#Рейтинг|05 сезон]]",
        [6] = "[[Шостий сезон#Рейтинг|06 сезон]]",
        [7] = "[[Сьомий сезон#Рейтинг|07 сезон]]",
        [8] = "[[Восьмий сезон#Рейтинг|08 сезон]]",
        [9] = "[[Дев'ятий сезон#Рейтинг|09 сезон]]"
     }
 
     for season = 1, 9 do
        local data = get_season_data(season, player_name)
        local row = htmlTable:tag('tr')
 
         row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(season_links[season])
 
        if data then
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.place)
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.points)
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.winrate)
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.games)
        else
            for i = 1, 4 do
                row:tag('td'):css('text-align', 'center'):css('padding', '8px'):css('color', '#666'):wikitext('-')
            end
        end
     end
     end
   
 
    table.insert(output, '|-')
     return tostring(htmlTable)
   
    for _, data in ipairs(season_data) do
        table.insert(output, '| ' .. data.place)
    end
   
    table.insert(output, '|}')
   
     return table.concat(output, '\n')
end
end


return p
return p

Поточна версія на 17:08, 9 серпня 2026

Документацію для цього модуля можна створити у Модуль:FetchData/документація

local p = {}

-- ================================================
-- КЕШУВАННЯ
-- ================================================

local page_cache = {}
local table_cache = {}

local function get_page_content(page_name)
    if page_cache[page_name] then
        return page_cache[page_name]
    end

    local title = mw.title.new(page_name)
    if not title or not title.exists then
        page_cache[page_name] = nil
        return nil
    end

    local content = title:getContent()
    page_cache[page_name] = content
    return content
end

local function get_table_from_page(page_name)
    if table_cache[page_name] then
        return table_cache[page_name]
    end

    local content = get_page_content(page_name)
    if not content then
        table_cache[page_name] = nil
        return nil
    end

    local table_start = mw.ustring.find(content, "{|")
    local table_end = mw.ustring.find(content, "|}", table_start)

    if not table_start or not table_end then
        table_cache[page_name] = nil
        return nil
    end

    local table_content = mw.ustring.sub(content, table_start, table_end + 1)
    table_cache[page_name] = table_content
    return table_content
end

-- ================================================
-- УТИЛІТИ
-- ================================================

local function error_output(context, error_details)
    local safe_details = error_details or "N/A"
    return string.format("<span style='color:red; font-weight:bold;'>[Error: %s. Details: %s]</span>",
        context or "Unknown", safe_details)
end

-- ШВИДКА ВЕРСІЯ: патерни тільки з ASCII-символів, тому string.gsub безпечний
local function clean_wikilinks(text)
    if not text then return nil end
    text = string.gsub(text, "%[%[([^%]|]+)|([^%]]+)%]%]", "%2")
    text = string.gsub(text, "%[%[([^%]]+)%]%]", "%1")
    return mw.text.trim(text)
end

local function strip_html_tags(text)
    if not text then return text end
    text = mw.ustring.gsub(text, "<span[^>]*>", "")
    text = mw.ustring.gsub(text, "</span>", "")
    return mw.text.trim(text)
end

local function parse_table_rows(table_content)
    local rows = {}
    for row in mw.ustring.gmatch(table_content, "|-\n(.-)\n|-") do
        table.insert(rows, row)
    end
    local last_row = mw.ustring.match(table_content, "|-\n(.-)%s*|}")
    if last_row then
        table.insert(rows, last_row)
    end
    return rows
end

local function parse_row_cells(row)
    local cells = {}

    for line in mw.ustring.gmatch(row, "[^\n]+") do
        line = mw.text.trim(line)

        if line ~= "|-" and line ~= "" then
            line = mw.ustring.gsub(line, "^|%s*", "")

            if line ~= "" then
                table.insert(cells, line)
            end
        end
    end

    return cells
end

local function find_player_in_table(table_content, player_name, column_index)
    if not table_content then return nil end

    local escaped = mw.ustring.gsub(player_name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
    local player_pattern = "%[%[" .. escaped .. "%]%]"

    local rows = parse_table_rows(table_content)

    for _, row in ipairs(rows) do
        if mw.ustring.find(row, player_pattern) then
            local cells = parse_row_cells(row)
            if column_index and column_index <= #cells then
                return cells[column_index]
            end
            return cells
        end
    end

    return nil
end

-- ================================================
-- SEASON RESULT
-- ================================================

function p.season_result(frame)
    local season = frame.args.season
    local player = frame.args.player

    local season_titles = {
        "Перший сезон", "Другий сезон", "Третій сезон",
        "Четвертий сезон", "П'ятий сезон", "Шостий сезон",
        "Сьомий сезон", "Восьмий сезон", "Дев'ятий сезон"
    }

    local season_title = season_titles[tonumber(season)]
    if not season_title then return "??" end

    local content = get_page_content(season_title)
    if not content then return "??" end

    local rating_section = mw.ustring.match(content, "==%s*Рейтинг%s*==.-{|%s*class%s*=%s*\"wikitable sortable\"(.-)|}")
    if not rating_section then return "--" end

    local pattern = "|%s*(%d+)%s*|%s*%[%[" .. mw.ustring.gsub(player, "([%(%)%.%-%+%[%]])", "%%%1") .. "%]%]"
    local direct_pattern = "|%s*(%d+)%s*|%s*" .. mw.ustring.gsub(player, "([%(%)%.%-%+%[%]])", "%%%1") .. "%s*"

    local rank = mw.ustring.match(rating_section, pattern) or mw.ustring.match(rating_section, direct_pattern)

    return rank or "--"
end

-- ================================================
-- БАЗОВА СТАТИСТИКА
-- ================================================

function p.games_count(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0" end

    local result = find_player_in_table(get_table_from_page("Статистика"), player_name, 2)
    return result or "0"
end

function p.wins_count(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0" end

    local result = find_player_in_table(get_table_from_page("Статистика"), player_name, 3)
    return result or "0"
end

function p.losses_count(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0" end

    local result = find_player_in_table(get_table_from_page("Статистика"), player_name, 4)
    return result or "0"
end

function p.win_rate_colored(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0%" end

    local win_rate = find_player_in_table(get_table_from_page("Статистика"), player_name, 5)
    if not win_rate then return "0%" end

    local rate_num = tonumber(mw.ustring.match(win_rate, "([%d%.]+)"))
    if not rate_num then return win_rate end

    local color = "#4caf50"
    if rate_num < 40 then
        color = "indianred"
    elseif rate_num < 50 then
        color = "orange"
    end

    if not mw.ustring.find(win_rate, "%%") then
        win_rate = win_rate .. "%"
    end

    return string.format('<span style="color:%s;">%s</span>', color, win_rate)
end

function p.recruiter(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "Не вказано" end

    local table_content = get_table_from_page("Гравці")
    if not table_content then return "Не вказано" end

    local cells = find_player_in_table(table_content, player_name)
    if not cells or #cells < 2 then return "Не вказано" end

    local raw = mw.text.trim(cells[2])
    if raw == "Відсутній" or raw == "-" or raw == "" then
        return "Не вказано"
    end

    return raw
end

function p.date_added(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "Лише Бог знає" end

    local raw = find_player_in_table(get_table_from_page("Гравці"), player_name, 3)

    if not raw or raw == "" or raw == "-" or raw == "Відсутній" then
        return "Лише Бог знає"
    end

    local day, month, year = mw.ustring.match(raw, "(%d+)%.(%d+)%.(%d+)")

    if day and month and year then
        local end_date = os.time({year=2024, month=12, day=1})
        local start_date = os.time({year=tonumber(year), month=tonumber(month), day=tonumber(day)})
        local days_diff = math.floor((end_date - start_date) / 86400)

        return string.format("%s (%d днів)", raw, days_diff)
    end

    return "Лише Бог знає"
end

function p.foundation(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0₴" end

    local raw = find_player_in_table(get_table_from_page("Фундація"), player_name, 2)

    if not raw or raw == "" or raw == "-" then return "0₴" end

    raw = mw.ustring.gsub(raw, "[^%d%s]", "")
    raw = mw.text.trim(raw)

    return (raw or "0") .. " ₴"
end

function p.prize_pool(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0₴" end

    local raw = find_player_in_table(get_table_from_page("Призовий_фонд"), player_name, 2)

    if not raw or raw == "" or raw == "-" then return "0₴" end

    raw = mw.ustring.gsub(raw, "[^%d%s]", "")
    raw = mw.text.trim(raw)

    return (raw or "0") .. " ₴"
end

function p.finalist(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0/9" end

    local raw = find_player_in_table(get_table_from_page("Фіналіст"), player_name, 2)

    if not raw or raw == "" or raw == "-" then return "0/9" end

    local number_only = mw.ustring.gsub(raw, "[^%d]", "")
    local count = tonumber(number_only)

    if count then
        return string.format("%d/9", count)
    end

    return "0/9"
end

function p.foty_rating(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "—" end

    local content = get_page_content("Період")
    if not content then return "—" end

    local foty_section = mw.ustring.match(content, "==+%s*Фінал Року%s*==+(.-)\n==")
    if not foty_section then
        foty_section = mw.ustring.match(content, "==+%s*Фінал Року%s*==+(.*)")
    end

    if not foty_section then return "—" end

    local table_start = mw.ustring.find(foty_section, "{|")
    local table_end = mw.ustring.find(foty_section, "|}", table_start)

    if not table_start or not table_end then return "—" end

    local table_content = mw.ustring.sub(foty_section, table_start, table_end + 1)
    local cells = find_player_in_table(table_content, player_name)

    if not cells or #cells < 3 then return "—" end

    local place = strip_html_tags(cells[1]) or "—"
    local rating = strip_html_tags(cells[3]) or "—"

    rating = mw.ustring.gsub(rating, "[^%d%-]", "")
    rating = mw.text.trim(rating)

    if rating and rating ~= "" then
        return rating .. " (" .. place .. " місце)"
    end

    return "—"
end

-- ================================================
-- ТИТУЛИ ТА НАГОРОДИ
-- ================================================

local tournament_dates = {
    ["Women's Closed Cup I"] = "15.03.2023",
    ["Men's Closed Cup I"] = "05.04.2023",
    ["Combined Closed Cup I"] = "21.04.2023",
    ["Score Open Battle I"] = "07.05.2023",
    ["Women's Closed Cup II"] = "11.05.2023",
    ["Men's Closed Cup II"] = "27.05.2023",
    ["Combined Closed Cup II"] = "02.06.2023",
    ["Score Open Battle II"] = "17.06.2023",
    ["Mafia Closed Cup I"] = "12.11.2023",
    ["Mafia Closed Cup I Online"] = "28.01.2024",
    ["My Closest Circle I"] = "01.01.2023",
    ["Get Names 01"] = "08.10.2023",
    ["Get Names 02"] = "03.12.2023",
    ["Get Names 03"] = "10.03.2024",
    ["Get Names 04"] = "27.04.2024",
    ["Get Names 05"] = "14.07.2024",
    ["Get Names 06"] = "21.07.2024",
    ["Get Names 07"] = "08.09.2024",
    ["Get Names 08"] = "19.09.2024",
    ["Get Names 09"] = "29.09.2024",
    ["Перший сезон (фінал)"] = "09.09.2023",
    ["Перший сезон (рейтинг)"] = "09.09.2023",
    ["Другий сезон (фінал)"] = "28.10.2023",
    ["Другий сезон (рейтинг)"] = "28.10.2023",
    ["Третій сезон (фінал)"] = "23.12.2023",
    ["Третій сезон (рейтинг)"] = "23.12.2023",
    ["Четвертий сезон (фінал)"] = "10.02.2024",
    ["Четвертий сезон (рейтинг)"] = "10.02.2024",
    ["П'ятий сезон (фінал)"] = "06.04.2024",
    ["П'ятий сезон (рейтинг)"] = "06.04.2024",
    ["Шостий сезон (фінал)"] = "11.05.2024",
    ["Шостий сезон (рейтинг)"] = "11.05.2024",
    ["Сьомий сезон (фінал)"] = "06.07.2024",
    ["Сьомий сезон (рейтинг)"] = "06.07.2024",
    ["Восьмий сезон (фінал)"] = "10.08.2024",
    ["Восьмий сезон (рейтинг)"] = "10.08.2024",
    ["Дев'ятий сезон (фінал)"] = "28.09.2024",
    ["Дев'ятий сезон (рейтинг)"] = "28.09.2024",
    ["Фінал Року"] = "13.10.2024"
}

local function get_medal_icon(value)
    if not value or value == "" or value == " " then return nil end
    value = mw.text.trim(value)

    if value == "" then return nil
    elseif value == "1" then return "[[Файл:Gold.png|20px|link=]]"
    elseif value == "2" then return "[[Файл:Silver.png|20px|link=]]"
    elseif value == "3" then return "[[Файл:Bronze.png|20px|link=]]"
    elseif value == "4" then return "[[Файл:Finalist.png|20px|link=]]"
    elseif value == "S" or mw.ustring.match(value, "^S%d") then return "[[Файл:Star.png|20px|link=]]"
    else return nil
    end
end

local function to_roman(num)
    local romans = {"I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX"}
    return romans[tonumber(num)] or num
end

local function get_tournament_link(header_name)
    local season_num = mw.ustring.match(header_name, "([А-Яа-яЁёІіЇїЄєҐґ']+) сезон %(фінал%)")
    if not season_num then
        season_num = mw.ustring.match(header_name, "([А-Яа-яЁёІіЇїЄєҐґ']+) сезон %(рейтинг%)")
    end

    if season_num then
        local season_map = {
            ["Перший"] = 1, ["Другий"] = 2, ["Третій"] = 3,
            ["Четвертий"] = 4, ["П'ятий"] = 5, ["Шостий"] = 6,
            ["Сьомий"] = 7, ["Восьмий"] = 8, ["Дев'ятий"] = 9
        }

        local season_number = season_map[season_num]
        if season_number then
            local roman = to_roman(season_number)
            local type_text = mw.ustring.find(header_name, "фінал") and "Фінал." or "Рейтинг."
            local clean_name = mw.ustring.gsub(header_name, "%s*%(фінал%)%s*", "")
            clean_name = mw.ustring.gsub(clean_name, "%s*%(рейтинг%)%s*", "")
            clean_name = mw.text.trim(clean_name)

            return string.format("[[%s|%s сезон. %s]]", clean_name, roman, type_text)
        end
    end

    return string.format("[[%s]]", header_name)
end

local function get_player_row(page_title, player_name)
    local table_content = get_table_from_page(page_title)
    if not table_content then return nil end

    local first_row = mw.ustring.match(table_content, "{|[^\n]*\n!(.-)[\n]")
    local headers = {}

    if first_row then
        for header in mw.ustring.gmatch(first_row, "([^|]+)") do
            local trimmed = mw.text.trim(header)
            if trimmed ~= "" then
                table.insert(headers, trimmed)
            end
        end
    end

    if #headers == 0 then return nil end

    local cells = find_player_in_table(table_content, player_name)
    if not cells then return nil end

    return {headers = headers, cells = cells}
end

function p.player_image(frame)
    local nickname = frame.args.nickname or frame.args[1] or mw.title.getCurrentTitle().text
    local player_file = "Player_" .. nickname .. ".png"
    local placeholder = "MCC_Placeholder.png"

    local title = mw.title.new("File:" .. player_file)

    local file_to_use
    if title and title.exists then
        file_to_use = player_file
    else
        file_to_use = placeholder
    end

    return string.format("[[File:%s|none|alt=Фотографія гравця|center|360px]]", file_to_use)
end

function p.titles(frame)
    local name = frame.args.player
    if not name or name == "" then return "''Відсутні''" end

    local titles_data = get_player_row("Титули", name)
    if not titles_data then return "''Відсутні''" end

    local prize_data = get_player_row("Призові", name)
    if not prize_data then return error_output("Prize Data Missing", name) end

    local results = {}

    for i = 2, #titles_data.cells do
        local cell_value = titles_data.cells[i]
        local header = titles_data.headers[i]
        local prize_value = prize_data.cells[i]

        if cell_value and cell_value ~= "" and cell_value ~= "-" and header then
            local medal = get_medal_icon(cell_value)

            if medal then
                local prize_amount = "0 ₴"

                if prize_value and prize_value ~= "" and prize_value ~= "-" then
                    prize_value = mw.ustring.gsub(prize_value, "₴", "")
                    prize_value = mw.text.trim(prize_value)
                    if prize_value ~= "0" then
                        prize_amount = prize_value .. " ₴"
                    end
                end

                local date = tournament_dates[header] or "01.01.2023"
                local tournament_link = get_tournament_link(header)

                table.insert(results, {
                    date = date,
                    medal = medal,
                    tournament = tournament_link,
                    prize = prize_amount
                })
            end
        end
    end

    if #results == 0 then return "''Відсутні''" end

    local table_html = {
        '{| class="wikitable sortable" style="font-size: 14.5px;"',
        '|-',
        '! style="" | Дата',
        '! style="text-align: left;" | Місце і турнір',
        '! style="text-align: left;" | Приз'
    }

    for _, result in ipairs(results) do
        table.insert(table_html, '|-')
        table.insert(table_html, string.format(
            '| style="width:80px; text-align:center;" | %s || style="text-align:left; padding-left:10px;" | %s %s || style="text-align:right;" | %s',
            result.date, result.medal, result.tournament, result.prize
        ))
    end

    table.insert(table_html, '|}')
    return table.concat(table_html, '\n')
end

-- ================================================
-- ЦІКАВІ ФАКТИ
-- ================================================

local pages_to_search = {
    "Перший сезон", "Другий сезон", "Третій сезон", "Четвертий сезон",
    "П'ятий сезон", "Шостий сезон", "Сьомий сезон", "Восьмий сезон",
    "Дев'ятий сезон", "Mafia Closed Circle", "Ten's Games", "Alternatywa",
    "Майстерня Стратегічного Саморозвитку", "Get Names 01", "Get Names 02",
    "Get Names 03", "Get Names 04", "Get Names 05", "Get Names 06",
    "Get Names 07", "Get Names 08", "Get Names 09", "Women's Closed Cup I",
    "Women's Closed Cup II", "Men's Closed Cup I", "Men's Closed Cup II",
    "Combined Closed Cup I", "Combined Closed Cup II", "Score Open Battle I",
    "Score Open Battle II", "Mafia Closed Cup I", "Mafia Closed Cup I Online",
    "My Closest Circle I", "Фінал Року"
}

local function get_facts_section(page_title)
    local content = get_page_content(page_title)
    if not content then return nil end

    local section_start = mw.ustring.find(content, "==%s*[Цц]ікаві%s*[Фф]акти%s*==")
    if not section_start then return nil end

    local section_end = mw.ustring.find(content, "\n==[^=]", section_start + 1)

    local section_content
    if section_end then
        section_content = mw.ustring.sub(content, section_start, section_end - 1)
    else
        section_content = mw.ustring.sub(content, section_start)
    end

    return section_content
end

local function fact_contains_player(fact, player_name)
    local escaped_name = mw.ustring.gsub(player_name, "([%^%$%(%)%%%.%[%]%*%+%-%?])", "%%%1")
    local pattern1 = "%[%[" .. escaped_name .. "%]%]"
    local pattern2 = "%[%[" .. escaped_name .. "|[^%]]+%]%]"

    return mw.ustring.find(fact, pattern1) or mw.ustring.find(fact, pattern2)
end

local function extract_facts(section_content)
    local facts = {}
    for fact in mw.ustring.gmatch(section_content, "%*[^\n]+") do
        table.insert(facts, fact)
    end
    return facts
end

function p.facts(frame)
    local player_name = frame.args.player or frame.args[1] or mw.title.getCurrentTitle().text

    local result_parts = {}

    for _, page_title in ipairs(pages_to_search) do
        local section = get_facts_section(page_title)

        if section then
            local facts = extract_facts(section)
            local player_facts = {}

            for _, fact in ipairs(facts) do
                if fact_contains_player(fact, player_name) then
                    table.insert(player_facts, fact)
                end
            end

            if #player_facts > 0 then
                local block = string.format("==== %s ====\n", page_title)
                for _, fact in ipairs(player_facts) do
                    block = block .. fact .. "\n"
                end
                table.insert(result_parts, string.format('<div style="margin-bottom: 20px;">\n%s</div>', block))
            end
        end
    end

    if #result_parts == 0 then return "" end

    return table.concat(result_parts, "\n")
end

-- ================================================
-- ЗАПИСИ ІГОР
-- ================================================

local function get_tournament_link_games(short_name, full_name)
    if not short_name or short_name == "" then
        return "Невідомо"
    end

    if short_name == "Фанова гра" then
        return "Фанова гра"
    end

    if full_name then
        full_name = clean_wikilinks(full_name)
    end

    if full_name and full_name ~= "" and full_name ~= short_name then
        return "[[" .. full_name .. "|" .. short_name .. "]]"
    else
        return "[[" .. short_name .. "]]"
    end
end

local function parse_games_row_cells(row)
    local cells = {}
    row = string.gsub(row, "^%s*|%s*", "")
    for cell in string.gmatch(row .. "||", "(.-)%s*||%s*") do
        table.insert(cells, mw.text.trim(cell))
    end
    return cells
end

-- Кеш: таблиця розбирається ОДИН раз на сторінку
local games_rows_cache = nil
local player_games_cache = {}

local function get_games_rows()
    if games_rows_cache then return games_rows_cache end

    local table_content = get_table_from_page("Ігри")
    if not table_content then return nil end

    local rows = {}
    for row in string.gmatch(table_content, "|-\n([^\n]+)") do
        if not string.match(row, "^%s*!") then
            local cells = parse_games_row_cells(row)
            if #cells >= 17 then
                local names = {}
                for i = 6, 15 do
                    names[i] = clean_wikilinks(cells[i])
                end
                table.insert(rows, { cells = cells, names = names })
            end
        end
    end

    games_rows_cache = rows
    return rows
end

local function has_unknown_roles(cells)
    local res = cells[16]
    if not res or mw.text.trim(res) == "" then return true end
    return string.find(res, "?", 1, true) ~= nil
end

local function get_player_role(player_name, entry)
    if has_unknown_roles(entry.cells) then return "—" end

    local names = entry.names
    for i = 6, 11 do
        if names[i] == player_name then return "Мир" end
    end
    if names[12] == player_name then return "Шер" end
    for i = 13, 14 do
        if names[i] == player_name then return "Маф" end
    end
    if names[15] == player_name then return "Дон" end

    return "—"
end

local function get_player_result(role, game_result)
    if not game_result or game_result == "" then return "—" end

    game_result = mw.text.trim(game_result)

    if string.find(game_result, "?", 1, true) then return "—" end

    if game_result == "Місто" or game_result == "Мирні" then
        return (role == "Мир" or role == "Шер") and "Перемога" or "Поразка"
    end

    if game_result == "Мафія" then
        return (role == "Маф" or role == "Дон") and "Перемога" or "Поразка"
    end

    return "—"
end

local function get_player_games_list(player_name)
    if player_games_cache[player_name] then
        return player_games_cache[player_name]
    end

    local rows = get_games_rows()
    if not rows then return nil end

    local games = {}

    for _, entry in ipairs(rows) do
        local found = false
        for i = 6, 15 do
            if entry.names[i] == player_name then
                found = true
                break
            end
        end

        if found then
            local cells = entry.cells
            local role = get_player_role(player_name, entry)
            local result = get_player_result(role, cells[16])

            local time_raw = cells[5] or ""
            local time_formatted = time_raw
            local minutes, seconds = string.match(time_raw, "(%d+)%s*хв%s*(%d+)%s*с")
            if minutes and seconds then
                time_formatted = minutes .. ":" .. string.format("%02d", tonumber(seconds))
            end

            table.insert(games, {
                short = cells[3] or "",
                tournament = cells[4] or "",
                time = time_formatted,
                role = role,
                result = result,
                link = cells[17] or ""
            })
        end
    end

    player_games_cache[player_name] = games
    return games
end

function p.player_games(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then
        return error_output("No Player Name", "")
    end

    local games = get_player_games_list(player_name)
    if not games then
        return error_output("Cannot Load Games", "Ігри")
    end

    if #games == 0 then
        return "''Ігор не знайдено''"
    end

    local htmlTable = mw.html.create('table')
        :addClass('wikitable sortable')
        :css('width', '100%')
        :css('font-size', '14.5px')

    local headerRow = htmlTable:tag('tr')
    headerRow:tag('th'):wikitext('Подія')
    headerRow:tag('th'):wikitext('Роль')
    headerRow:tag('th'):wikitext('Час')
    headerRow:tag('th'):wikitext('🏆')
    headerRow:tag('th'):wikitext('Запис')

    for _, game in ipairs(games) do
        local row = htmlTable:tag('tr')
        local unknown = (game.result == "—")

        -- Турнір
        row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
            :wikitext(get_tournament_link_games(game.short, game.tournament))

        -- Роль
        row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
            :tag('span')
                :css('color', unknown and '#777' or '#fff')
                :wikitext(game.role)

        -- Час
        row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')
            :wikitext(game.time)

        -- Результат
        local resultCell = row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')

        if game.result == "Перемога" then
            resultCell:tag('span')
                :css('color', '#4caf50')
                :wikitext('В')
        elseif game.result == "Поразка" then
            resultCell:tag('span')
                :css('color', 'indianred')
                :wikitext('П')
        else
            resultCell:tag('span')
                :css('color', '#777')
                :wikitext('—')
        end

        -- Запис
        local linkCell = row:tag('td')
            :css('text-align', 'center')
            :css('padding', '8px')

        if game.link and game.link ~= "" then
            linkCell:wikitext('<span class="game-record-btn">[' .. game.link .. ' ▶︎]</span>')
        end
    end

    return tostring(htmlTable)
end

function p.games_count_records(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then return "0" end

    local games = get_player_games_list(player_name)
    return games and tostring(#games) or "0"
end

-- ================================================
-- ДОСЯГНЕННЯ В СЕЗОНАХ
-- ================================================

local function get_season_data(season_number, player_name)
    local season_names = {
        [1] = "Перший сезон", [2] = "Другий сезон", [3] = "Третій сезон",
        [4] = "Четвертий сезон", [5] = "П'ятий сезон", [6] = "Шостий сезон",
        [7] = "Сьомий сезон", [8] = "Восьмий сезон", [9] = "Дев'ятий сезон"
    }

    local season_name = season_names[season_number]
    if not season_name then return nil end

    local content = get_page_content(season_name)
    if not content then return nil end

    local rating_section = mw.ustring.match(content, "==+%s*Рейтинг%s*==+(.-)\n==")
    if not rating_section then
        rating_section = mw.ustring.match(content, "==+%s*Рейтинг%s*==+(.*)")
    end
    if not rating_section then return nil end

    local table_start = mw.ustring.find(rating_section, "{|")
    local table_end = mw.ustring.find(rating_section, "|}", table_start)
    if not table_start or not table_end then return nil end

    local table_content = mw.ustring.sub(rating_section, table_start, table_end + 1)

    local current_row = {}
    local all_rows = {}

    for line in mw.ustring.gmatch(table_content .. "\n", "([^\n]*)\n") do
        line = mw.text.trim(line)

        if line == "|-" then
            if #current_row > 0 then
                table.insert(all_rows, current_row)
            end
            current_row = {}
        elseif mw.ustring.match(line, "^|[^-}]") then
            local cell_content = mw.ustring.gsub(line, "^|%s*", "")
            table.insert(current_row, cell_content)
        end
    end

    if #current_row > 0 then
        table.insert(all_rows, current_row)
    end

    for _, row in ipairs(all_rows) do
        if #row >= 5 then
            local player_cell = clean_wikilinks(row[2])

            if player_cell == player_name then
                return {
                    place = mw.text.trim(row[1]),
                    points = mw.text.trim(row[3]),
                    games = mw.text.trim(row[4]),
                    winrate = mw.text.trim(row[5])
                }
            end
        end
    end

    return nil
end

function p.season_achievements(frame)
    local player_name = frame.args.player
    if not player_name or player_name == "" then
        return "<span style='color:red;'>Ім'я гравця не вказано</span>"
    end

    local htmlTable = mw.html.create('table')
        :addClass('wikitable sortable')
        :css('width', '100%')
        :css('font-size', '14.5px')

    local headerRow = htmlTable:tag('tr')
    headerRow:tag('th'):wikitext('Сезон')
    headerRow:tag('th'):wikitext('Місце')
    headerRow:tag('th'):wikitext('Бали')
    headerRow:tag('th'):wikitext('% перемог')
    headerRow:tag('th'):wikitext('Ігор')

    local season_links = {
        [1] = "[[Перший сезон#Рейтинг|01 сезон]]",
        [2] = "[[Другий сезон#Рейтинг|02 сезон]]",
        [3] = "[[Третій сезон#Рейтинг|03 сезон]]",
        [4] = "[[Четвертий сезон#Рейтинг|04 сезон]]",
        [5] = "[[П'ятий сезон#Рейтинг|05 сезон]]",
        [6] = "[[Шостий сезон#Рейтинг|06 сезон]]",
        [7] = "[[Сьомий сезон#Рейтинг|07 сезон]]",
        [8] = "[[Восьмий сезон#Рейтинг|08 сезон]]",
        [9] = "[[Дев'ятий сезон#Рейтинг|09 сезон]]"
    }

    for season = 1, 9 do
        local data = get_season_data(season, player_name)
        local row = htmlTable:tag('tr')

        row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(season_links[season])

        if data then
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.place)
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.points)
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.winrate)
            row:tag('td'):css('text-align', 'center'):css('padding', '8px'):wikitext(data.games)
        else
            for i = 1, 4 do
                row:tag('td'):css('text-align', 'center'):css('padding', '8px'):css('color', '#666'):wikitext('-')
            end
        end
    end

    return tostring(htmlTable)
end

return p