참고: 설정을 저장한 후에 바뀐 점을 확인하기 위해서는 브라우저의 캐시를 새로 고쳐야 합니다.

  • 파이어폭스 / 사파리: Shift 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5 또는 Ctrl-R을 입력 (Mac에서는 ⌘-R)
  • 구글 크롬: Ctrl-Shift-R키를 입력 (Mac에서는 ⌘-Shift-R)
  • 인터넷 익스플로러 / 엣지: Ctrl 키를 누르면서 새로 고침을 클릭하거나, Ctrl-F5를 입력.
  • 오페라: Ctrl-F5를 입력.
(function () {
    'use strict';

    var HISTORY_EVENT_MANAGER_BUILD = '20260710-history-events-manager-fix53';
    var DEFAULT_ERAS = [
        '1950', '1960', '1970', '1980', '1990', '2000', '2010', '2020',
        '2030', '2040', '2050', '2060', '2070', '2080', '2090', '2100'
    ];

    function escapeHtml(value) {
        return String(value == null ? '' : value)
            .replace(/&/g, '&')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/"/g, '&quot;')
            .replace(/'/g, '&#039;');
    }

    function formatMwApiFailure(code, data) {
        var parts = [];
        var xhr = data && (data.xhr || data.jqXHR);

        if (data && data.error && data.error.info) parts.push(data.error.info);
        if (data && data.exception) parts.push(String(data.exception));
        if (xhr) {
            if (xhr.status) parts.push('HTTP ' + xhr.status + (xhr.statusText ? ' ' + xhr.statusText : ''));
            if (xhr.responseJSON && xhr.responseJSON.error && xhr.responseJSON.error.info) parts.push(xhr.responseJSON.error.info);
            if (!xhr.status && xhr.responseText) parts.push(String(xhr.responseText).slice(0, 180));
        }
        if (!parts.length && data && data.textStatus) parts.push(String(data.textStatus));
        if (!parts.length) parts.push(String(code || 'unknown'));
        return parts.filter(Boolean).join(' / ');
    }

    function replaceYearTemplate(template, year) {
        return String(template || '').replace(/\{year\}/g, String(year || ''));
    }

    function normalizeYear(value) {
        var text = String(value || '').trim().replace(/[^0-9]/g, '').slice(0, 4);
        return text.length === 4 ? text : '';
    }

    function normalizeEvent(item) {
        item = item && typeof item === 'object' ? item : {};
        return {
            period: String(item.period || item.date || item.range || item.year || '').trim(),
            title: String(item.title || item.event || item.name || item.label || '').trim(),
            page: String(item.page || item.wiki_title || item.title_page || '').trim(),
            external_url: String(item.external_url || item.url || '').trim(),
            major: item.major === true || item.type === 'major',
            note: String(item.note || '').trim(),
            enabled: item.enabled !== false
        };
    }

    function makeEmptyState(era) {
        var start = parseInt(era || '1950', 10);
        var years = [];
        var i;

        if (!isFinite(start)) start = 1950;
        for (i = 0; i < 10; i += 1) {
            years.push({ year: String(start + i), label: String(start + i), items: [] });
        }

        return {
            version: 1,
            era: parseInt(era || start, 10),
            description: '시대 ' + era + '년 역사적 사건 패널 데이터. Manage:History_Events에서 편집한다.',
            years: years
        };
    }

    function normalizeState(payload, era) {
        var state = makeEmptyState(era);
        var sourceYears = [];

        if (!payload || typeof payload !== 'object') return state;

        if (Array.isArray(payload.years)) {
            sourceYears = payload.years;
        } else if (payload.events && typeof payload.events === 'object') {
            Object.keys(payload.events).forEach(function (year) {
                sourceYears.push({ year: year, label: year, items: payload.events[year] });
            });
        }

        state.version = payload.version || 1;
        state.era = payload.era || payload.year || state.era;
        state.description = payload.description || state.description;
        state.years = sourceYears.map(function (row) {
            var year;
            var items;

            if (typeof row === 'string' || typeof row === 'number') row = { year: String(row), items: [] };
            row = row && typeof row === 'object' ? row : {};
            year = normalizeYear(row.year || row.key || row.label) || String(row.year || row.key || row.label || '').trim();
            items = Array.isArray(row.items) ? row.items : (Array.isArray(row.events) ? row.events : []);

            return {
                year: year,
                label: String(row.label || year).trim() || year,
                items: items.map(normalizeEvent)
            };
        }).filter(function (row) { return !!row.year; });

        if (!state.years.length) state.years = makeEmptyState(era).years;
        return state;
    }

    function compareYears(a, b) {
        var av = parseInt(a && a.year, 10);
        var bv = parseInt(b && b.year, 10);
        if (isFinite(av) && isFinite(bv) && av !== bv) return av - bv;
        return String(a && a.year || '').localeCompare(String(b && b.year || ''), ['ko', 'en'], { numeric: true });
    }

    function HistoryEventManager(root) {
        this.root = root;
        this.eras = String(root.getAttribute('data-years') || DEFAULT_ERAS.join(' ')).split(/[\s,|]+/).filter(Boolean);
        this.era = root.getAttribute('data-year') || this.eras[0] || '1950';
        this.titleTemplate = root.getAttribute('data-title-template') || 'MediaWiki:{year}_History_Events.json';
        this.urlTemplate = root.getAttribute('data-url-template') || '/index.php?title=MediaWiki:{year}_History_Events.json&action=raw&ctype=application/json';
        this.state = null;
        this.status = '';
        this.query = '';
        this.loading = false;
        this.sourceExists = false;
    }

    HistoryEventManager.prototype.getTitle = function () {
        return replaceYearTemplate(this.titleTemplate, this.era);
    };

    HistoryEventManager.prototype.getUrl = function () {
        return replaceYearTemplate(this.urlTemplate, this.era);
    };

    HistoryEventManager.prototype.init = function () {
        this.root.classList.add('history-event-manager-ready');
        this.loadYear(this.era);
    };

    HistoryEventManager.prototype.setStatus = function (text) {
        this.status = text || '';
        var el = this.root.querySelector('.history-event-status');
        if (el) el.textContent = this.status;
    };

    HistoryEventManager.prototype.renderShell = function () {
        this.root.innerHTML = '<div class="history-event-loading">' + escapeHtml(this.status || 'loading...') + '</div>';
    };

    HistoryEventManager.prototype.loadYear = function (year) {
        var self = this;
        this.era = String(year || this.era || '1950');
        this.loading = true;
        this.status = this.era + '년 역사적 사건 데이터 불러오는 중...';
        this.renderShell();

        return fetch(this.getUrl(), { credentials: 'same-origin', cache: 'no-cache' })
            .then(function (res) {
                if (!res.ok) throw new Error('HTTP ' + res.status);
                return res.text();
            })
            .then(function (text) {
                var trimmed = String(text || '').trim();
                if (!trimmed) throw new Error('empty');
                self.state = normalizeState(JSON.parse(trimmed), self.era);
                self.sourceExists = true;
                self.status = self.getTitle() + ' 불러옴';
            }).catch(function () {
                self.state = makeEmptyState(self.era);
                self.sourceExists = false;
                self.status = self.getTitle() + ' 새 문서';
            }).finally(function () {
                self.loading = false;
                self.render();
            });
    };

    HistoryEventManager.prototype.getFilteredYears = function () {
        var q = this.query.trim().toLowerCase();
        var years = (this.state && this.state.years ? this.state.years : []).slice();

        if (!q) return years;
        return years.map(function (year) {
            var copy = {
                year: year.year,
                label: year.label,
                items: (year.items || []).filter(function (item) {
                    return [year.year, year.label, item.period, item.title, item.page, item.external_url, item.note, item.major ? 'major' : '', item.enabled === false ? 'disabled' : 'enabled'].join(' ').toLowerCase().indexOf(q) !== -1;
                })
            };
            return copy;
        }).filter(function (year) {
            return year.items.length || String(year.year + ' ' + year.label).toLowerCase().indexOf(q) !== -1;
        });
    };

    HistoryEventManager.prototype.render = function () {
        var state = this.state || makeEmptyState(this.era);
        var years = this.getFilteredYears();
        var eventCount = (state.years || []).reduce(function (sum, year) { return sum + ((year.items || []).length); }, 0);
        var enabledCount = (state.years || []).reduce(function (sum, year) {
            return sum + (year.items || []).filter(function (item) { return item.enabled !== false; }).length;
        }, 0);

        this.root.innerHTML = '' +
            '<div class="history-event-shell">' +
                '<div class="history-event-head">' +
                    '<div class="history-event-title">History Event Manager</div>' +
                    '<div class="history-event-summary">' + escapeHtml(this.era) + '년 · 연도 ' + (state.years || []).length + ' · 사건 ' + eventCount + ' · 사용 ' + enabledCount + ' · ' + (this.sourceExists ? '문서 있음' : '새 문서') + '</div>' +
                '</div>' +
                '<div class="history-event-toolbar">' +
                    '<label>시대 <select class="history-event-year-select">' + this.eras.map(function (year) {
                        return '<option value="' + escapeHtml(year) + '"' + (year === this.era ? ' selected' : '') + '>' + escapeHtml(year) + '</option>';
                    }, this).join('') + '</select></label>' +
                    '<input type="search" class="history-event-search" placeholder="연도, 날짜, 사건명, 문서명, 메모 검색" value="' + escapeHtml(this.query) + '">' +
                    '<button type="button" class="history-event-add-year">연도 추가</button>' +
                    '<button type="button" class="history-event-reload">다시 불러오기</button>' +
                    '<button type="button" class="history-event-save">저장</button>' +
                    '<span class="history-event-status">' + escapeHtml(this.status) + '</span>' +
                '</div>' +
                '<div class="history-event-note">시대 문서의 역사적 사건 패널 데이터입니다. 저장 대상: ' + escapeHtml(this.getTitle()) + '</div>' +
                '<div class="history-event-body">' + (years.length ? years.map(this.renderYearSection, this).join('') : '<div class="history-event-empty">검색 결과가 없습니다.</div>') + '</div>' +
            '</div>';

        this.bind();
    };

    HistoryEventManager.prototype.renderYearSection = function (year) {
        var rows = (year.items || []).map(function (item, index) {
            return this.renderEventRow(year, item, index);
        }, this).join('');

        if (!rows) rows = '<tr><td colspan="9" class="history-event-empty-cell">이 연도에는 사건이 없습니다.</td></tr>';

        return '' +
            '<section class="history-event-year" data-year="' + escapeHtml(year.year) + '">' +
                '<div class="history-event-year-head">' +
                    '<div class="history-event-year-fields">' +
                        '<input type="text" class="history-event-year-value" data-year-field="year" value="' + escapeHtml(year.year) + '" maxlength="4" title="탭 내부 키">' +
                        '<input type="text" class="history-event-year-label" data-year-field="label" value="' + escapeHtml(year.label || year.year) + '" title="패널에 보이는 연도 버튼 라벨">' +
                        '<span class="history-event-year-count">' + (year.items || []).length + '개</span>' +
                    '</div>' +
                    '<div class="history-event-year-actions">' +
                        '<button type="button" class="history-event-add" data-year="' + escapeHtml(year.year) + '">사건 추가</button>' +
                        '<button type="button" class="history-event-year-up" data-year="' + escapeHtml(year.year) + '">위</button>' +
                        '<button type="button" class="history-event-year-down" data-year="' + escapeHtml(year.year) + '">아래</button>' +
                        '<button type="button" class="history-event-year-delete" data-year="' + escapeHtml(year.year) + '">연도 삭제</button>' +
                    '</div>' +
                '</div>' +
                '<div class="history-event-table-wrap">' +
                    '<table class="history-event-table">' +
                        '<thead><tr>' +
                            '<th class="history-event-order-col">순서</th>' +
                            '<th class="history-event-period-col">날짜/기간</th>' +
                            '<th class="history-event-title-col">사건명</th>' +
                            '<th class="history-event-page-col">문서</th>' +
                            '<th class="history-event-url-col">외부 URL</th>' +
                            '<th class="history-event-check-col">강조</th>' +
                            '<th class="history-event-check-col">사용</th>' +
                            '<th class="history-event-note-col">메모</th>' +
                            '<th class="history-event-action-col">작업</th>' +
                        '</tr></thead>' +
                        '<tbody data-year="' + escapeHtml(year.year) + '">' + rows + '</tbody>' +
                    '</table>' +
                '</div>' +
            '</section>';
    };

    HistoryEventManager.prototype.renderEventRow = function (year, item, index) {
        return '' +
            '<tr data-year="' + escapeHtml(year.year) + '" data-index="' + index + '">' +
                '<td class="history-event-order-col"><button type="button" class="history-event-up">↑</button><button type="button" class="history-event-down">↓</button></td>' +
                '<td class="history-event-period-col"><input type="text" data-field="period" value="' + escapeHtml(item.period) + '" placeholder="1936.07.17–1940..."></td>' +
                '<td class="history-event-title-col"><input type="text" data-field="title" value="' + escapeHtml(item.title) + '" placeholder="사건명"></td>' +
                '<td class="history-event-page-col"><input type="text" data-field="page" value="' + escapeHtml(item.page) + '" placeholder="문서명"></td>' +
                '<td class="history-event-url-col"><input type="text" data-field="external_url" value="' + escapeHtml(item.external_url) + '" placeholder="https://..."></td>' +
                '<td class="history-event-check-col"><input type="checkbox" data-field="major"' + (item.major ? ' checked' : '') + '></td>' +
                '<td class="history-event-check-col"><input type="checkbox" data-field="enabled"' + (item.enabled !== false ? ' checked' : '') + '></td>' +
                '<td class="history-event-note-col"><input type="text" data-field="note" value="' + escapeHtml(item.note) + '"></td>' +
                '<td class="history-event-action-col"><button type="button" class="history-event-delete">삭제</button></td>' +
            '</tr>';
    };

    HistoryEventManager.prototype.bind = function () {
        var self = this;
        var select = this.root.querySelector('.history-event-year-select');
        var search = this.root.querySelector('.history-event-search');
        var save = this.root.querySelector('.history-event-save');
        var reload = this.root.querySelector('.history-event-reload');
        var addYear = this.root.querySelector('.history-event-add-year');

        if (select) select.addEventListener('change', function () { self.query = ''; self.loadYear(select.value); });
        if (search) search.addEventListener('input', function () {
            self.query = search.value;
            self.render();
            var next = self.root.querySelector('.history-event-search');
            if (next) {
                next.focus();
                next.setSelectionRange(next.value.length, next.value.length);
            }
        });
        if (save) save.addEventListener('click', function () { self.save(); });
        if (reload) reload.addEventListener('click', function () { self.loadYear(self.era); });
        if (addYear) addYear.addEventListener('click', function () { self.addYear(); });

        this.root.querySelectorAll('.history-event-year').forEach(function (section) {
            var yearKey = section.getAttribute('data-year') || '';
            section.querySelectorAll('[data-year-field]').forEach(function (input) {
                input.addEventListener('input', function () { self.updateYear(section, input); });
                input.addEventListener('change', function () { self.updateYear(section, input); self.render(); });
            });
            section.querySelectorAll('.history-event-add').forEach(function (button) {
                button.addEventListener('click', function () { self.addEvent(yearKey); });
            });
            section.querySelectorAll('.history-event-year-delete').forEach(function (button) {
                button.addEventListener('click', function () { self.deleteYear(yearKey); });
            });
            section.querySelectorAll('.history-event-year-up').forEach(function (button) {
                button.addEventListener('click', function () { self.moveYear(yearKey, -1); });
            });
            section.querySelectorAll('.history-event-year-down').forEach(function (button) {
                button.addEventListener('click', function () { self.moveYear(yearKey, 1); });
            });
        });

        this.root.querySelectorAll('.history-event-table tbody tr[data-index]').forEach(function (tr) {
            tr.querySelectorAll('input[data-field]').forEach(function (input) {
                input.addEventListener('input', function () { self.updateEvent(tr, input); });
                input.addEventListener('change', function () { self.updateEvent(tr, input); });
            });
            var del = tr.querySelector('.history-event-delete');
            var up = tr.querySelector('.history-event-up');
            var down = tr.querySelector('.history-event-down');
            if (del) del.addEventListener('click', function () { self.deleteEvent(tr); });
            if (up) up.addEventListener('click', function () { self.moveEvent(tr, -1); });
            if (down) down.addEventListener('click', function () { self.moveEvent(tr, 1); });
        });
    };

    HistoryEventManager.prototype.findYear = function (yearKey) {
        return (this.state.years || []).find(function (row) { return row.year === yearKey; }) || null;
    };

    HistoryEventManager.prototype.findEvent = function (tr) {
        var year = this.findYear(tr.getAttribute('data-year'));
        var index = parseInt(tr.getAttribute('data-index') || '-1', 10);
        if (!year || index < 0 || !year.items || !year.items[index]) return null;
        return { year: year, index: index, item: year.items[index] };
    };

    HistoryEventManager.prototype.updateYear = function (section, input) {
        var oldKey = section.getAttribute('data-year') || '';
        var row = this.findYear(oldKey);
        var field = input.getAttribute('data-year-field');
        var value = input.value;
        var newKey;

        if (!row) return;
        if (field === 'year') {
            newKey = normalizeYear(value) || String(value || '').trim();
            row.year = newKey;
            if (!row.label || row.label === oldKey) row.label = newKey;
            section.setAttribute('data-year', newKey);
        } else if (field === 'label') {
            row.label = String(value || '').trim();
        }
        this.setStatus('수정됨');
    };

    HistoryEventManager.prototype.updateEvent = function (tr, input) {
        var row = this.findEvent(tr);
        var field = input.getAttribute('data-field');
        var value;

        if (!row) return;
        value = input.type === 'checkbox' ? input.checked : input.value;

        if (field === 'period') row.item.period = String(value || '').trim();
        else if (field === 'title') row.item.title = String(value || '').trim();
        else if (field === 'page') row.item.page = String(value || '').trim();
        else if (field === 'external_url') row.item.external_url = String(value || '').trim();
        else if (field === 'major') row.item.major = !!value;
        else if (field === 'enabled') row.item.enabled = !!value;
        else if (field === 'note') row.item.note = String(value || '').trim();

        this.setStatus('수정됨');
    };

    HistoryEventManager.prototype.addYear = function () {
        var value = window.prompt('추가할 연도를 입력하세요.', this.era);
        var year = normalizeYear(value) || String(value || '').trim();
        if (!year) return;
        if (this.findYear(year)) {
            this.setStatus('이미 있는 연도입니다: ' + year);
            return;
        }
        this.state.years.push({ year: year, label: year, items: [] });
        this.setStatus('연도 추가됨');
        this.render();
    };

    HistoryEventManager.prototype.deleteYear = function (yearKey) {
        var index = (this.state.years || []).findIndex(function (row) { return row.year === yearKey; });
        if (index < 0) return;
        this.state.years.splice(index, 1);
        this.setStatus('연도 삭제됨');
        this.render();
    };

    HistoryEventManager.prototype.moveYear = function (yearKey, direction) {
        var list = this.state.years || [];
        var index = list.findIndex(function (row) { return row.year === yearKey; });
        var next = index + direction;
        var item;
        if (index < 0 || next < 0 || next >= list.length) return;
        item = list.splice(index, 1)[0];
        list.splice(next, 0, item);
        this.setStatus('연도 순서 변경됨');
        this.render();
    };

    HistoryEventManager.prototype.addEvent = function (yearKey) {
        var year = this.findYear(yearKey);
        if (!year) return;
        year.items = year.items || [];
        year.items.push(normalizeEvent({ enabled: true }));
        this.setStatus('사건 추가됨');
        this.render();
    };

    HistoryEventManager.prototype.deleteEvent = function (tr) {
        var row = this.findEvent(tr);
        if (!row) return;
        row.year.items.splice(row.index, 1);
        this.setStatus('사건 삭제됨');
        this.render();
    };

    HistoryEventManager.prototype.moveEvent = function (tr, direction) {
        var row = this.findEvent(tr);
        var list;
        var next;
        var item;
        if (!row) return;
        list = row.year.items || [];
        next = row.index + direction;
        if (next < 0 || next >= list.length) return;
        item = list.splice(row.index, 1)[0];
        list.splice(next, 0, item);
        this.setStatus('사건 순서 변경됨');
        this.render();
    };

    HistoryEventManager.prototype.toText = function () {
        var out = normalizeState(this.state, this.era);
        out.era = parseInt(this.era, 10) || this.era;
        out.description = this.state.description || ('시대 ' + this.era + '년 역사적 사건 패널 데이터. Manage:History_Events에서 편집한다.');
        out.years = (out.years || []).map(function (year) {
            return {
                year: year.year,
                label: year.label || year.year,
                items: (year.items || []).map(function (item) {
                    var row = {
                        period: item.period || '',
                        title: item.title || '',
                        page: item.page || '',
                        external_url: item.external_url || '',
                        major: !!item.major,
                        enabled: item.enabled !== false
                    };
                    if (item.note) row.note = item.note;
                    return row;
                })
            };
        });
        return JSON.stringify(out, null, 2);
    };

    HistoryEventManager.prototype.save = function () {
        var self = this;
        var title = this.getTitle();

        if (!window.mw || !mw.Api) {
            this.setStatus('저장 실패: mw.Api를 사용할 수 없습니다.');
            return;
        }

        this.setStatus(title + ' 저장 중...');
        new mw.Api().postWithToken('csrf', {
            action: 'edit',
            title: title,
            text: this.toText(),
            summary: 'Update ' + this.era + ' history events',
            format: 'json'
        }).done(function () {
            self.sourceExists = true;
            self.setStatus(title + ' 저장 완료');
            self.render();
            if (window.NationsPanel && typeof window.NationsPanel.refresh === 'function') {
                window.NationsPanel.refresh(document);
            }
        }).fail(function (code, data) {
            self.setStatus('저장 실패: ' + formatMwApiFailure(code, data));
        });
    };

    function init(root) {
        (root || document).querySelectorAll('.history-event-manager').forEach(function (node) {
            if (node.getAttribute('data-history-event-manager-ready') === '1') return;
            node.setAttribute('data-history-event-manager-ready', '1');
            new HistoryEventManager(node).init();
        });
    }

    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', function () { init(document); });
    } else {
        init(document);
    }

    if (window.mw && mw.hook) {
        mw.hook('wikipage.content').add(function ($content) {
            init($content && $content[0] ? $content[0] : document);
        });
    }

    window.HistoryEventManager = {
        build: HISTORY_EVENT_MANAGER_BUILD,
        init: init
    };
}());