
let assetList_file=[
'js/kw/kw4.0c.js',
"js/codemirror/5.65.17/codemirror.min.css",
"js/codemirror/5.65.17/show-hint.min.css",
"js/codemirror/5.65.17/matchesonscrollbar.css",
"js/codemirror/5.65.17/codemirror.min.js",
"js/codemirror/5.65.17/javascript.min.js",
"js/codemirror/5.65.17/matchbrackets.min.js",
"js/codemirror/5.65.17/searchcursor.js",
"js/codemirror/5.65.17/match-highlighter.min.js",
"js/codemirror/5.65.17/comment.min.js",
"js/codemirror/5.65.17/continuecomment.min.js",
"js/codemirror/5.65.17/show-hint.min.js",
"js/codemirror/5.65.17/javascript-hint.min.js",
"js/codemirror/5.65.17/ggbppt4.0c.css",
]

// offline_libpath/ttttk 已经由页面头部的 detectOfflineRoot 探测确定(4.0 之前是这里自己再探测一次, 4.0 起直接复用, 避免和头部探测口径不一致)
let assetList = assetList_file.map(name => (ttttk ? offline_libpath : online_libpath) + name);


let isAssetsLoaded = false;
let editor = null;

function showggb_code(){
if (!isAssetsLoaded) {
    // this.innerText = "正在加载资源...";
    showSuccess('正在加载资源...');
    this.disabled = true;

    loadAssetsSequentially(assetList, () => {
        isAssetsLoaded = true;
        this.disabled = false;
        initCodeMirror();
        initCodeMirror2();
        showPopup();
    });

} else {
    showSuccess('成功显示...');
    showPopup();
}
}



// --- 3. 资源加载器 (核心逻辑) ---
function loadAssetsSequentially(list, callback) {
let index = 0;
function loadNext() {
    if (index >= list.length) {
        callback();
        return;
    }
    const url = list[index];
    index++;
    loadSingleAsset(url, loadNext);
}
loadNext();
}

function loadSingleAsset(url, onLoad) {
    let element;
    if (url.endsWith(".css")) {
        element = document.createElement("link");
        element.rel = "stylesheet";
        element.href = url;
    } else {
        element = document.createElement("script");
        element.src = url;
    }
    element.onload = onLoad;
    element.onerror = () => {
        console.error("加载失败:", url);
        onLoad(); // 即使失败也继续，防止卡死
    };
    document.head.appendChild(element);
}

// --- 代码页 tab 条渲染 (ggb 编辑器和 js 编辑器各自独立, 互不影响) ---
// 4.0c: 删除按钮改成 × 且靠右放(和 + 分开, 防误点), × 左边加 ↩ 撤销按钮, 可恢复最近删除的代码页
function renderCodeTabs(barId, pages, currentIdx, switchFn, addFn, deleteFn, undoFn, canUndo) {
    const bar = document.getElementById(barId);
    if (!bar) return;
    let html = '';
    pages.forEach((_, i) => {
        html += `<button type="button" class="code-tab${i === currentIdx ? ' active' : ''}" data-idx="${i}">${i + 1}</button>`;
    });
    html += `<button type="button" class="code-tab-add" title="新增代码页">＋</button>`;
    html += `<span class="code-tab-spacer"></span>`;
    html += `<button type="button" class="code-tab-undo" title="撤销删除, 恢复最近删除的代码页"${canUndo ? '' : ' disabled'}>↩</button>`;
    html += `<button type="button" class="code-tab-del" title="删除当前代码页">×</button>`;
    bar.innerHTML = html;
    bar.querySelectorAll('.code-tab').forEach(btn => {
        btn.onclick = () => switchFn(parseInt(btn.dataset.idx, 10));
    });
    bar.querySelector('.code-tab-add').onclick = addFn;
    bar.querySelector('.code-tab-undo').onclick = undoFn;
    bar.querySelector('.code-tab-del').onclick = deleteFn;
}

// 被删代码页的撤销栈(仅当前会话有效): 元素为 {idx, content}
let ggbCodeDeleted = [];
let jsCodeDeleted = [];

function renderGgbTabs() { renderCodeTabs('ggbCodeTabs', ggbCodePages, ggbCodeCurrent, switchGgbCodePage, addGgbCodePage, deleteGgbCodePage, undoDeleteGgbCodePage, ggbCodeDeleted.length > 0); }
function renderJsTabs() { renderCodeTabs('jsCodeTabs', jsCodePages, jsCodeCurrent, switchJsCodePage, addJsCodePage, deleteJsCodePage, undoDeleteJsCodePage, jsCodeDeleted.length > 0); }

function switchGgbCodePage(idx) {
    if (!editor || idx === ggbCodeCurrent) return;
    ggbCodePages[ggbCodeCurrent] = editor.getValue();
    ggbCodeCurrent = idx;
    editor.setValue(ggbCodePages[ggbCodeCurrent]);
    renderGgbTabs();
}
function addGgbCodePage() {
    if (editor) ggbCodePages[ggbCodeCurrent] = editor.getValue();
    ggbCodePages.push('');
    ggbCodeCurrent = ggbCodePages.length - 1;
    if (editor) editor.setValue('');
    renderGgbTabs();
}
function deleteGgbCodePage() {
    if (ggbCodePages.length <= 1) return;
    if (editor) ggbCodePages[ggbCodeCurrent] = editor.getValue();  // 先回写, 撤销时才能恢复最新内容
    ggbCodeDeleted.push({ idx: ggbCodeCurrent, content: ggbCodePages[ggbCodeCurrent] });
    ggbCodePages.splice(ggbCodeCurrent, 1);
    if (ggbCodeCurrent >= ggbCodePages.length) ggbCodeCurrent = ggbCodePages.length - 1;
    if (editor) editor.setValue(ggbCodePages[ggbCodeCurrent]);
    renderGgbTabs();
}
function undoDeleteGgbCodePage() {
    if (!ggbCodeDeleted.length) return;
    if (editor) ggbCodePages[ggbCodeCurrent] = editor.getValue();
    const rec = ggbCodeDeleted.pop();
    const idx = Math.min(rec.idx, ggbCodePages.length);
    ggbCodePages.splice(idx, 0, rec.content);
    ggbCodeCurrent = idx;
    if (editor) editor.setValue(ggbCodePages[ggbCodeCurrent]);
    renderGgbTabs();
}

function switchJsCodePage(idx) {
    if (!editor2 || idx === jsCodeCurrent) return;
    jsCodePages[jsCodeCurrent] = editor2.getValue();
    jsCodeCurrent = idx;
    editor2.setValue(jsCodePages[jsCodeCurrent]);
    renderJsTabs();
}
function addJsCodePage() {
    if (editor2) jsCodePages[jsCodeCurrent] = editor2.getValue();
    jsCodePages.push('');
    jsCodeCurrent = jsCodePages.length - 1;
    if (editor2) editor2.setValue('');
    renderJsTabs();
}
function deleteJsCodePage() {
    if (jsCodePages.length <= 1) return;
    if (editor2) jsCodePages[jsCodeCurrent] = editor2.getValue();  // 先回写, 撤销时才能恢复最新内容
    jsCodeDeleted.push({ idx: jsCodeCurrent, content: jsCodePages[jsCodeCurrent] });
    jsCodePages.splice(jsCodeCurrent, 1);
    if (jsCodeCurrent >= jsCodePages.length) jsCodeCurrent = jsCodePages.length - 1;
    if (editor2) editor2.setValue(jsCodePages[jsCodeCurrent]);
    renderJsTabs();
}
function undoDeleteJsCodePage() {
    if (!jsCodeDeleted.length) return;
    if (editor2) jsCodePages[jsCodeCurrent] = editor2.getValue();
    const rec = jsCodeDeleted.pop();
    const idx = Math.min(rec.idx, jsCodePages.length);
    jsCodePages.splice(idx, 0, rec.content);
    jsCodeCurrent = idx;
    if (editor2) editor2.setValue(jsCodePages[jsCodeCurrent]);
    renderJsTabs();
}

// 保存前把当前正在编辑的那一页从 CodeMirror 回写到数组里 (getOfflineHtmlStrx 保存前会调用, 弹窗没打开过时 editor/editor2 为 null, 安全跳过)
function syncCodePagesFromEditors() {
    if (editor) ggbCodePages[ggbCodeCurrent] = editor.getValue();
    if (editor2) jsCodePages[jsCodeCurrent] = editor2.getValue();
}

// --- 4. 初始化功能 (资源加载后执行) ---
function initCodeMirror() {
    (function() {
"use strict";
// const openBrackets = "([{";
// const closeBrackets = ")]}";
// const bracketPairs = { '(': ')', '[': ']', '{': '}' };
// const colorCount = 8;

CodeMirror.defineMode("javascript-rb", function(config) {
    const jsMode = CodeMirror.getMode(config, "javascript");
    const mode = {
        startState: function() {
            return { bracketStack: [], jsState: CodeMirror.startState(jsMode) };
        },
        copyState: function(state) {
            return { bracketStack: state.bracketStack.slice(), jsState: CodeMirror.copyState(jsMode, state.jsState) };
        },
        token: function(stream, state) {
            const char = stream.peek();
            if ("([{".indexOf(char) > -1) {
                const level = state.bracketStack.length;
                state.bracketStack.push(char);
                stream.next();
                return `bracket-level-${level % 8}`;
            }
            if (")]}".indexOf(char) > -1) {
                stream.next();
                const stackSize = state.bracketStack.length;
                if (stackSize > 0 && { '(':')', '[':']', '{':'}' }[state.bracketStack[stackSize - 1]] === char) {
                    state.bracketStack.pop();
                    const level = state.bracketStack.length;
                    return `bracket-level-${level % 8}`;
                } else {
                    return "mismatch-bracket";
                }
            }
            return jsMode.token(stream, state.jsState);
        }
    };
if (jsMode.lineComment) mode.lineComment = jsMode.lineComment;
if (jsMode.blockCommentStart) mode.blockCommentStart = jsMode.blockCommentStart;
if (jsMode.blockCommentEnd) mode.blockCommentEnd = jsMode.blockCommentEnd;
return mode;
});


editor = CodeMirror.fromTextArea(document.getElementById("ggbInput"), {
mode: "javascript-rb",
lineNumbers: true,
matchBrackets: true,
indentUnit: 2,
tabSize: 2,
extraKeys: {
"Ctrl-Q": "toggleComment",        // 行注释
"Cmd-Q": "toggleComment",         // Mac
    "Ctrl-D": function(cm) {
        const selections = cm.listSelections();
        cm.operation(function() {
            selections.forEach(sel => {
                let from = sel.from(), to = sel.to();
                if (from.line === to.line && from.ch === to.ch) {
                    const lineText = cm.getLine(from.line);
                    cm.replaceRange(lineText + "\n", { line: from.line + 1, ch: 0 });
                } else {
                    const text = cm.getRange(from, to);
                    cm.replaceRange(text, { line: to.line + 1, ch: 0 });
                }
            });
        });
    }
},
highlightSelectionMatches: {
    showToken: /\w/,
    annotateScrollbar: true
},
hintOptions: { completeSingle: false }, // 不自动选中第一个
});

// 多页: 用当前激活的代码页内容填充编辑器, 而不是依赖 textarea 里的原始文本
editor.setValue(ggbCodePages[ggbCodeCurrent] || '');
renderGgbTabs();

setTimeout(() => { editor.refresh(); }, 100);

const fontslider1 = document.getElementById("fontslider1");
editor.getWrapperElement().style.fontSize = fontslider1.value + "px";
editor.refresh();
let ffsize = fontslider1.value;
fontslider1.addEventListener("input", function() {
editor.getWrapperElement().style.fontSize = this.value + "px";
editor.refresh();
ffsize = fontslider1.value;
});


////////////////////////////////////
function customHint(cm) {
const cur = cm.getCursor();
const token = cm.getTokenAt(cur);
let word = token.string;
let from = CodeMirror.Pos(cur.line, token.start);
let to = CodeMirror.Pos(cur.line, token.end);

if (token.type === "comment") {
    const match = word.match(/([\w\u4e00-\u9fa5]+)$/);
    if (match) {
    word = match[1];
    const startOffset = token.end - match[1].length;
    from = CodeMirror.Pos(cur.line, startOffset);
    } else {
    word = "";
    }
} else {
    word = word.replace(/[^\w\u4e00-\u9fa5]/g, "");
}
word = word.toLowerCase();

const matches = functionList.filter(f => f.kw.toLowerCase().includes(word));
const listSource = matches.length ? matches : functionList;

const list = listSource.map(f => ({
    text: f.text,
    displayText: f.kw,
    render: function (el) {
    el.style.display = "flex";
    el.style.justifyContent = "space-between";
    el.style.alignItems = "flex-start";
    el.style.fontSize = `${ffsize}px`;
    el.style.maxWidth = "900px";
    el.style.whiteSpace = "normal";
    el.style.wordBreak = "break-word";
    el.style.padding = "3px 6px";

    const left = document.createElement("div");
    left.textContent = f.kw;
    left.style.flex = "1";
    left.style.fontWeight = "bold";
    left.style.marginRight = "12px";
    left.style.color = "#222";
    left.style.minWidth = "280px";
    left.style.maxWidth = "400px";

    const right = document.createElement("div");
    let htmlDisplay = f.displayText?.split("<br>")[0]?.split("||||")[0] || "";
    right.innerHTML = htmlDisplay;
    right.style.flex = "1";
    right.style.color = "#333";
    right.style.textAlign = "left";
    right.style.overflow = "hidden";
    right.style.textOverflow = "ellipsis";
    right.style.maxHeight = "80px";
    right.style.opacity = 0.9;


    left.style.zIndex = 99999;
    right.style.zIndex = 99999;
    el.style.zIndex = 99999;
    el.appendChild(left);
    el.appendChild(right);
    }
}));

return { list, from, to };
}



editor.on("inputRead", function (cm, change) {
if (change.text[0].match(/[\w\u4e00-\u9fa5]/)) {
    cm.showHint({ hint: customHint, completeSingle: false });
}
});

// 多页持久化: 无论通过什么方式改动(输入/粘贴/撤销), 都把当前页内容同步回数组
editor.on("change", function (cm) {
ggbCodePages[ggbCodeCurrent] = cm.getValue();
});

let tooltipEl = null;
let hideTimer = null;

function showTooltipAt(tokenPos, htmlContent) {
removeTooltip();
const coords = editor.charCoords(tokenPos, "page");

tooltipEl = document.createElement("div");
tooltipEl.style.position = "absolute";
tooltipEl.style.left = coords.left + "px";
tooltipEl.style.background = "rgba(240,240,240,1)";
tooltipEl.style.color = "rgba(0,0,0,1)";
tooltipEl.style.padding = "8px";
tooltipEl.style.borderRadius = "6px";
tooltipEl.style.fontSize = `${ffsize}px`;
tooltipEl.style.maxWidth = "800px";
tooltipEl.style.maxHeight = "600px";
tooltipEl.style.zIndex = 9999;
tooltipEl.style.whiteSpace = "normal";
tooltipEl.style.overflowY = "auto";
tooltipEl.style.overflowX = "hidden";
tooltipEl.style.boxShadow = "0 2px 8px rgba(0,0,0,0.5)";
tooltipEl.style.userSelect = "text";
tooltipEl.innerHTML = htmlContent;

tooltipEl.addEventListener("mouseenter", () => {
    if (hideTimer) {
        clearTimeout(hideTimer);
        hideTimer = null;
    }
});
tooltipEl.addEventListener("mouseleave", () => {
    hideTimer = setTimeout(removeTooltip, 100);
});

document.body.appendChild(tooltipEl);

// 🔽 关键：在插入 DOM 后重新计算 top，让 tooltip 出现在上方
const tooltipHeight = tooltipEl.offsetHeight;
tooltipEl.style.top = (coords.top - tooltipHeight) + "px";  // 8px 为间距
}


// 移除 tooltip
function removeTooltip() {
if (tooltipEl) {
    tooltipEl.remove();
    tooltipEl = null;
}
if (hideTimer) {
    clearTimeout(hideTimer);
    hideTimer = null;
}
}

// 在 editor 中鼠标悬浮事件
editor.getWrapperElement().addEventListener("mouseover", (e) => {
const pos = editor.coordsChar({ left: e.clientX, top: e.clientY });
const token = editor.getTokenAt(pos);

if (!token.string) return;

const match = functionList.find(f => f.text === token.string);
if (match) {
    const htmlContent = `
    <div style="margin-top:4px;">
    ${match.kw}  &nbsp;&nbsp;
            <a href="https://translate.yandex.com/translate?lang=en-zh&url=https://geogebra.github.io/docs/manual/en/commands/${match.text}" target="_blank">中文翻译文档</a> &nbsp;&nbsp;
            <a href="https://geogebra.github.io/docs/manual/en/commands/${match.text}" target="_blank">英文官方文档</a>
        </div>
        <div>${match.displayText}</div>
    `;
    showTooltipAt({ line: pos.line, ch: token.start }, htmlContent);
}
});

// 鼠标离开 editor token
editor.getWrapperElement().addEventListener("mouseout", (e) => {
if (!tooltipEl) return;

// 延迟隐藏 tooltip
hideTimer = setTimeout(removeTooltip, 100);
});

})();

    }

function showPopup() {
    const popup = document.getElementById('customPopup');
    popup.style.display = 'flex';
    // 每次显示时刷新一下编辑器，防止样式错乱
    if (editor) editor.refresh();
}

// // --- 5. 页面交互函数 (补充缺失的函数) ---
function runGgbCode() {
    if (!editor) return;
    const code = editor.getValue();
    let cleaned = code.replace(/\s*\/\/.*$/gm, '').replace(/,+\s*$/gm, '').replace(/;+\s*$/gm, '').replace(/^\s*[\r\n]/gm, '').trim();
    let lines = cleaned.split("\n");
    lines.forEach(line => {
    ggbApplet.evalCommand(line);
    });
}

function changeFontSize(size) {
    const cmDiv = document.querySelector('.CodeMirror');
    if(cmDiv) cmDiv.style.fontSize = size + 'px';
}

// --- 6. 弹窗拖拽逻辑 (不需要 Lazy Load，因为代码量很小) ---
// 放在这里比注入到 <script> 标签里更稳定、好调试
(function initDrag() {
    const popup = document.getElementById('customPopup');
    const header = document.getElementById('popupHeader');
    const closeBtn = document.getElementById('closePopupBtn');

    let isDragging = false;
    let startX, startY;
    let rafId = null;

    closeBtn.onclick = () => popup.style.display = 'none';

    header.addEventListener('mousedown', (e) => {
        isDragging = true;
        const rect = popup.getBoundingClientRect();

        // 将 transform 居中转为绝对定位，防止跳动
        if (window.getComputedStyle(popup).transform !== 'none') {
            popup.style.left = rect.left + 'px';
            popup.style.top = rect.top + 'px';
            popup.style.transform = 'none';
            popup.style.margin = '0';
        }

        startX = e.clientX - rect.left;
        startY = e.clientY - rect.top;
        e.preventDefault(); // 防止选中文本
    });

    document.addEventListener('mousemove', (e) => {
        if (!isDragging) return;
        if (rafId) return;

        rafId = requestAnimationFrame(() => {
            popup.style.left = (e.clientX - startX) + 'px';
            popup.style.top = (e.clientY - startY) + 'px';
            rafId = null;
        });
    });

    document.addEventListener('mouseup', () => {
        isDragging = false;
        if (rafId) {
            cancelAnimationFrame(rafId);
            rafId = null;
        }
    });
})();




////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////

let editor2 = null;


function showjs_code(){
if (!isAssetsLoaded) {
        // this.innerText = "正在加载资源...";
        showSuccess('正在加载资源...');
        this.disabled = true;

        loadAssetsSequentially(assetList, () => {
            isAssetsLoaded = true;
            this.disabled = false;
            initCodeMirror2();
            initCodeMirror();
            showPopup2();
        });

    } else {
        showSuccess('成功显示...');
        showPopup2();
    }
}


function initCodeMirror2() {
"use strict";
editor2 = CodeMirror.fromTextArea(document.getElementById("jsInput"), {
mode: "javascript",
lineNumbers: true,
matchBrackets: true,
indentUnit: 2,
tabSize: 2,
extraKeys: {
"Ctrl-Q": "toggleComment",        // 行注释
"Cmd-Q": "toggleComment",         // Mac
    "Ctrl-D": function(cm) {
        const selections = cm.listSelections();
        cm.operation(function() {
            selections.forEach(sel => {
                let from = sel.from(), to = sel.to();
                if (from.line === to.line && from.ch === to.ch) {
                    const lineText = cm.getLine(from.line);
                    cm.replaceRange(lineText + "\n", { line: from.line + 1, ch: 0 });
                } else {
                    const text = cm.getRange(from, to);
                    cm.replaceRange(text, { line: to.line + 1, ch: 0 });
                }
            });
        });
    }
},
highlightSelectionMatches: {
    showToken: /\w/,
    annotateScrollbar: true
},
hintOptions: { completeSingle: false }, // 不自动选中第一个
});

// 多页: 用当前激活的代码页内容填充编辑器, 而不是依赖 textarea 里的原始文本
editor2.setValue(jsCodePages[jsCodeCurrent] || '');
renderJsTabs();

setTimeout(() => { editor2.refresh(); }, 100);

const fontslider2 = document.getElementById("fontslider2");
editor2.getWrapperElement().style.fontSize = fontslider2.value + "px";
editor2.refresh();
fontslider2.addEventListener("input", function() {
editor2.getWrapperElement().style.fontSize = this.value + "px";
editor2.refresh();
setTimeout(() => { editor2.refresh(); }, 100);
});

// 多页持久化: 无论通过什么方式改动(输入/粘贴/撤销), 都把当前页内容同步回数组
editor2.on("change", function (cm) {
jsCodePages[jsCodeCurrent] = cm.getValue();
});

}

function showPopup2() {
const popup2 = document.getElementById('customPopup2');
popup2.style.display = 'flex';
// 每次显示时刷新一下编辑器，防止样式错乱
if (editor2) editor2.refresh();
}




(function initDrag2() {
const popup2 = document.getElementById('customPopup2');
const header2 = document.getElementById('popupHeader2');
const closeBtn2 = document.getElementById('closePopupBtn2');

let isDragging = false;
let startX, startY;
let rafId = null;

closeBtn2.onclick = () => popup2.style.display = 'none';

header2.addEventListener('mousedown', (e) => {
    isDragging = true;
    const rect = popup2.getBoundingClientRect();

    // 将 transform 居中转为绝对定位，防止跳动
    if (window.getComputedStyle(popup2).transform !== 'none') {
        popup2.style.left = rect.left + 'px';
        popup2.style.top = rect.top + 'px';
        popup2.style.transform = 'none';
        popup2.style.margin = '0';
    }

    startX = e.clientX - rect.left;
    startY = e.clientY - rect.top;
    e.preventDefault(); // 防止选中文本
});

document.addEventListener('mousemove', (e) => {
    if (!isDragging) return;
    if (rafId) return;

    rafId = requestAnimationFrame(() => {
        popup2.style.left = (e.clientX - startX) + 'px';
        popup2.style.top = (e.clientY - startY) + 'px';
        rafId = null;
    });
});

document.addEventListener('mouseup', () => {
    isDragging = false;
    if (rafId) {
        cancelAnimationFrame(rafId);
        rafId = null;
    }
});
})();



function runJsCode(){
if(!ggbApplet) return;
try {
    const fn = new Function("api", editor2.getValue());
    fn(ggbApplet);
} catch(err){
    alert("执行 JS 代码出错: " + err.message);
}
}
