1jQuery(function() {
2    const CDN_URL = 'https://cdn.jsdelivr.net/npm/openpgp@5.11.0/dist/openpgp.min.js';
3
4    /**
5     * Aggressive Sanitizer: This fixes the "Error during parsing" by
6     * reconstructing the PGP Armor block from scratch, ensuring the
7     * mandatory blank line after headers is present.
8     */
9    function sanitizeArmor(text) {
10        const lines = text.trim().split(/\r?\n/);
11        let headerLine = "";
12        let tailLine = "";
13        let headers = [];
14        let body = [];
15        let checksum = "";
16        let section = "START";
17
18        for (let i = 0; i < lines.length; i++) {
19            let line = lines[i].trim();
20            if (!line && section !== "BODY") continue;
21
22            if (line.startsWith('-----BEGIN PGP')) {
23                headerLine = line;
24                section = "HEADER";
25                continue;
26            }
27            if (line.startsWith('-----END PGP')) {
28                tailLine = line;
29                section = "END";
30                continue;
31            }
32
33            if (section === "HEADER") {
34                // If it looks like a header (contains ': '), keep it.
35                if (line.includes(': ')) {
36                    headers.push(line);
37                } else if (/^[A-Za-z0-9+/]/.test(line)) {
38                    // If it looks like Base64, the body started early (missing blank line)
39                    section = "BODY";
40                    body.push(line);
41                }
42            } else if (section === "BODY") {
43                if (line.startsWith('=')) {
44                    checksum = line;
45                } else {
46                    body.push(line);
47                }
48            }
49        }
50
51        // Reassemble: Header -> Attributes -> BLANK LINE -> Body -> Checksum -> Tail
52        let result = [headerLine];
53        if (headers.length > 0) result.push(...headers);
54        result.push(""); // The mandatory empty line
55        result.push(...body);
56        if (checksum) result.push(checksum);
57        result.push(tailLine);
58
59        return result.join('\n');
60    }
61
62    const readFileAsArrayBuffer = (file) => {
63        return new Promise((resolve, reject) => {
64            const reader = new FileReader();
65            reader.onload = () => resolve(reader.result);
66            reader.onerror = () => reject(reader.error);
67            reader.readAsArrayBuffer(file);
68        });
69    };
70
71    function loadOpenPGP(callback) {
72        if (window.openpgp) { callback(); return; }
73        jQuery.getScript(CDN_URL, function() { callback(); });
74    }
75
76    function isBinaryData(uint8) {
77        const header = Array.from(uint8.subarray(0, 4)).map(b => String.fromCharCode(b)).join('');
78        // Detect Zip (PK), PDF (%PDF), or PNG (0x89)
79        if (header.startsWith('PK') || header.startsWith('%PDF') || uint8[0] === 0x89) return true;
80        return uint8.subarray(0, 1024).some(byte => byte === 0);
81    }
82
83    const $wikiTextarea = jQuery('#wiki__text');
84
85    if ($wikiTextarea.length > 0) {
86        // Build UI
87        const $toolContainer = jQuery('<div class="pgpblock-edit-tools"></div>');
88        const $infoLabel = jQuery('<span class="pgp-info-label">�� <b>PGP Engine:</b></span>');
89        const $btnGroup = jQuery('<div class="pgp-modal-actions"></div>');
90        const $encButton = jQuery('<button type="button" class="pgp-btn pgp-btn-text">�� Encrypt/Decrypt Closest</button>');
91        const $fileButton = jQuery('<button type="button" class="pgp-btn pgp-btn-file">�� Encrypt & Insert File</button>');
92        const $fileInput = jQuery('<input type="file" style="display:none;" />');
93
94        $btnGroup.append($encButton).append($fileButton).append($fileInput);
95        $toolContainer.append($infoLabel).append($btnGroup);
96        $wikiTextarea.before($toolContainer);
97
98        // Build Modal
99        const $modalOverlay = jQuery(`
100            <div id="pgpblock-modal">
101                <div class="pgp-modal-content">
102                    <h3 id="pgpblock-modal-title">PGP Cryptography</h3>
103                    <p id="pgpblock-modal-desc">Enter passphrase:</p>
104                    <input type="password" id="pgpblock-modal-pass" autocomplete="off" placeholder="Passphrase"/>
105                    <div class="pgp-modal-actions">
106                        <button type="button" id="pgpblock-modal-cancel" class="pgp-btn pgp-btn-cancel">Cancel</button>
107                        <button type="button" id="pgpblock-modal-confirm" class="pgp-btn pgp-btn-text">Submit</button>
108                    </div>
109                </div>
110            </div>
111        `);
112        jQuery('body').append($modalOverlay);
113
114        let activeMacroResolve = null;
115        const handleConfirm = () => {
116            const val = jQuery('#pgpblock-modal-pass').val();
117            jQuery('#pgpblock-modal-pass').val('');
118            $modalOverlay.hide();
119            if (activeMacroResolve) activeMacroResolve(val);
120        };
121        jQuery('#pgpblock-modal-confirm').on('click', handleConfirm);
122        jQuery('#pgpblock-modal-cancel').on('click', () => { $modalOverlay.hide(); if (activeMacroResolve) activeMacroResolve(null); });
123        jQuery('#pgpblock-modal-pass').on('keydown', (e) => { if (e.key === 'Enter') { e.preventDefault(); handleConfirm(); } });
124
125        function getPassphrase(title) {
126            return new Promise((res) => {
127                activeMacroResolve = res;
128                jQuery('#pgpblock-modal-title').text(title);
129                $modalOverlay.css('display', 'flex');
130                setTimeout(() => jQuery('#pgpblock-modal-pass').focus(), 50);
131            });
132        }
133
134        // Action: File Encrypt
135        $fileButton.on('click', () => $fileInput.click());
136        $fileInput.on('change', async function(e) {
137            const file = e.target.files[0];
138            if (!file) return;
139            const pass = await getPassphrase(`Encrypt File: ${file.name}`);
140            if (!pass) { $fileInput.val(''); return; }
141            $fileButton.prop('disabled', true).text('Encrypting...');
142            loadOpenPGP(async function() {
143                try {
144                    const buf = await readFileAsArrayBuffer(file);
145                    const msg = await window.openpgp.createMessage({ binary: new Uint8Array(buf), filename: file.name });
146                    const enc = await window.openpgp.encrypt({ message: msg, passwords: [pass], format: 'armored' });
147                    const tag = `<pgpfile filename="${file.name}">\n${enc.trim()}\n</pgpfile>\n`;
148                    const el = $wikiTextarea[0], pos = el.selectionStart, text = $wikiTextarea.val();
149                    $wikiTextarea.val(text.substring(0, pos) + tag + text.substring(pos));
150                } catch (err) { alert(err.message); }
151                finally { $fileButton.prop('disabled', false).text('�� Encrypt & Insert File'); $fileInput.val(''); }
152            });
153        });
154
155        // Action: Encrypt/Decrypt
156        $encButton.on('click', async function(e) {
157            e.preventDefault();
158            const el = $wikiTextarea[0], caretPos = el.selectionStart, fullText = $wikiTextarea.val();
159            const pgpRegex = /<(pgp|gpg|pgpfile)([^>]*)>([\s\S]*?)<\/\1>/gi;
160            let blocks = []; let match;
161
162            while ((match = pgpRegex.exec(fullText)) !== null) {
163                const start = match.index, end = match.index + match[0].length;
164                let dist = (caretPos < start) ? (start - caretPos) : (caretPos > end ? caretPos - end : 0);
165                let fn = "";
166                const fnMatch = match[2].match(/filename=["']([^"']+)["']/i);
167                if (fnMatch) fn = fnMatch[1];
168                blocks.push({ start, end, tag: match[1], body: match[3].trim(), isEnc: match[3].includes('-----BEGIN PGP MESSAGE-----'), filename: fn, dist });
169            }
170
171            if (!blocks.length) return alert("No PGP blocks found.");
172            blocks.sort((a, b) => a.dist - b.dist);
173            const target = blocks[0];
174
175            let pass = await getPassphrase(target.isEnc ? "Decrypting Block" : "Encrypting Block");
176            if (!pass) return;
177
178            $encButton.prop('disabled', true).text('Processing...');
179            loadOpenPGP(async function() {
180                try {
181                    let resultBody = "";
182                    if (target.isEnc) {
183                        // SANITIZE before readMessage
184                        const armored = sanitizeArmor(target.body);
185                        const msg = await window.openpgp.readMessage({ armoredMessage: armored });
186                        const { data } = await window.openpgp.decrypt({
187                            message: msg, passwords: [pass], format: 'binary',
188                            config: { allowUnauthenticatedMessages: true }
189                        });
190
191                        if (target.tag === 'pgpfile' || isBinaryData(data)) {
192                            const blob = new Blob([data], { type: 'application/octet-stream' });
193                            const url = URL.createObjectURL(blob);
194                            const a = document.createElement('a');
195                            document.body.appendChild(a);
196                            a.style.display = 'none'; a.href = url;
197                            a.download = target.filename || "decrypted_file.bin";
198                            a.click();
199                            document.body.removeChild(a);
200                            URL.revokeObjectURL(url);
201                            resultBody = target.body;
202                        } else {
203                            resultBody = new TextDecoder().decode(data).trim();
204                        }
205                    } else {
206                        const msg = await window.openpgp.createMessage({ text: target.body });
207                        const enc = await window.openpgp.encrypt({ message: msg, passwords: [pass], format: 'armored' });
208                        resultBody = enc.trim();
209                    }
210
211                    const attr = target.filename ? ` filename="${target.filename}"` : "";
212                    const newBlock = `<${target.tag}${attr}>\n${resultBody}\n</${target.tag}>`;
213                    $wikiTextarea.val(fullText.substring(0, target.start) + newBlock + fullText.substring(target.end));
214                    el.selectionStart = el.selectionEnd = target.start;
215                } catch (err) { alert("PGP Error: " + err.message); console.error(err); }
216                finally { $encButton.prop('disabled', false).text('�� Encrypt/Decrypt Closest'); }
217            });
218        });
219    }
220});
221