console.log("loaded")

document.addEventListener("DOMContentLoaded", async function() {
    const targetElement = document.querySelector(".postform");
    if (!targetElement || !document.querySelector('.tegalogpost')) return;

    // ローディング表示（ログイン確認中）
    const loader = document.createElement("div");
    loader.id = "ex_uploader_loader";
    loader.style.cssText = "color:#999; font-size:0.9em; padding:4px 0;";
    loader.textContent = "読み込み中...";
    targetElement.appendChild(loader);

    // ログイン確認
    try {
        const loggedIn = await fetch('?mode=api&item=ISLOGGEDIN', { cache: 'no-store' }).then(r => r.text());
        if (loggedIn.trim() !== 'YES') {
            console.log('[tega-exuploader] 未ログインのため拡張アップローダーは表示されません');
            loader.remove();
            return;
        }
    } catch (e) {
        console.error('[tega-exuploader] ログイン状態の確認に失敗しました:', e);
        loader.remove();
        return;
    }

    // 設定を取得
    let config = { imageConvertOption: '0', resizeOption: '0', maxDimension: 1000, fileNamingMode: 1 };
    try {
        config = await fetch('tega-exuploader/uploader.php?getConfig=1').then(r => r.json());
    } catch (e) {
        console.error('[tega-exuploader] 設定の取得に失敗しました:', e);
    }

    // モード3の場合はユーザーIDを取得
    if (parseInt(config.fileNamingMode) === 3) {
        try {
            config.userId = (await fetch('?mode=api&item=USERID', { cache: 'no-store' }).then(r => r.text())).trim().replace(/[^\w\-]/g, '') || 'user';
        } catch (e) {
            console.error('[tega-exuploader] ユーザーIDの取得に失敗しました:', e);
            config.userId = 'user';
        }
    }

    loader.remove();

    const uploaderContainer = document.createElement("div");
    uploaderContainer.classList.add("ex_uploader");
    // インラインで非表示にしてからDOMに追加（CSSより確実）
    uploaderContainer.style.display = 'none';
    uploaderContainer.innerHTML = `
    <style>
        #dropArea { text-align: center; border: 2px dashed #ccc; }
        #previewArea img { max-height: 100px; }
        #overlay { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0,0,0,0.5); color: white; display: flex; justify-content: center; align-items: center; z-index: 9; }
        #overlay-content { font-size: 24px; }
        .settings-info { margin-bottom: 10px; color: #666; font-size: 0.7em; }
        /* トリミングモーダル */
        #cropModal { display:none; position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.8); z-index:10; justify-content:center; align-items:center; }
        #cropModal.active { display:flex; }
        #cropModalInner { background:#222; padding:12px; border-radius:8px; max-width:90vw; max-height:90vh; display:flex; flex-direction:column; gap:8px; }
        #cropCanvas { display:block; cursor:crosshair; user-select:none; touch-action:none; }
        #cropActions { display:flex; gap:8px; justify-content:flex-end; }
        #cropActions button { padding:6px 16px; border:none; border-radius:4px; cursor:pointer; font-size:0.95em; }
        #cropRotateLeft, #cropRotateRight { background:#555; color:#fff; margin-right:auto; }
        #cropConfirm { background:#4caf50; color:#fff; }
        #cropCancel  { background:#888;    color:#fff; }
        #cropInfo    { color:#aaa; font-size:0.85em; text-align:center; }
        .trim-btn { background:rgba(0,120,255,0.8); color:white; border:none; border-radius:50%; cursor:pointer; width:20px; height:20px; font-size:11px; line-height:20px; padding:0; }
        .camera-btn { display:block; width:100%; margin-top:4px; padding:6px 0; font-size:0.85em; background:#555; color:#fff; border:none; cursor:pointer; border-radius:3px; }
    </style>
    <div id="cropModal">
      <div id="cropModalInner">
        <canvas id="cropCanvas"></canvas>
        <div id="cropInfo">ドラッグで範囲を選択 / 内側をドラッグで移動 / 隅をドラッグでリサイズ</div>
        <div id="cropActions">
          <button type="button" id="cropRotateLeft"  title="左に90度回転">↺回転</button>
          <button type="button" id="cropRotateRight" title="右に90度回転">回転↻</button>
          <button type="button" id="cropCancel">キャンセル</button>
          <button type="button" id="cropConfirm">この範囲でトリミング</button>
        </div>
      </div>
    </div>
    <div id="dropArea">ここをクリック(タップ)<br>もしくはD＆D、またはCtrl+Vで画像を選択<br>「投稿する」で画像アップロード<br>加工はアップロード前に選択してください</div>
    <input type="file" id="fileInput" accept=".jpg,.jpeg,.gif,.png,.webp,.avif" multiple style="display:none;">
    <input type="file" id="cameraInput" accept="image/*" capture="environment" style="display:none;">
    <button type="button" class="camera-btn" id="cameraBtn">📷 カメラで撮影(モバイル向け)</button>
    <div id="previewArea"></div>
    <div>
        <label><input type="checkbox" id="createThumbnail" name="createThumbnail" checked>サムネイルを作成</label>
        <label><input type="checkbox" id="applyThumbnail" name="applyThumbnail" checked>加工をサムネイルに反映</label>
        <label><input type="checkbox" id="thumbnailOnlyTrim" name="thumbnailOnlyTrim" checked>サムネイルのみトリミング</label>
    </div>
    <div>
        <input type="checkbox" id="insertText">テキスト挿入
        <input type="text" id="customText" value="無断転載禁止/DO NOT REPOST">
    </div>
    <div>
        <label><input type="checkbox" id="applyWatermark" name="applyWatermark" value="yes">透かしを入れる</label>
        <select id="watermarkSelect">
            <option value="watermark.png">watermark.png</option>
        </select>
        <div id="watermarkOptions" style="display:none;">
            <label><input type="radio" name="watermarkPosition" value="center" checked>中央</label>
            <label><input type="radio" name="watermarkPosition" value="top-left">左上</label>
            <label><input type="radio" name="watermarkPosition" value="top-right">右上</label>
            <label><input type="radio" name="watermarkPosition" value="bottom-left">左下</label>
            <label><input type="radio" name="watermarkPosition" value="bottom-right">右下</label>
            <label><input type="radio" name="watermarkPosition" value="repeat">全体リピート</label>
        </div>
    </div>
    <div>
        <label><input type="checkbox" id="flagNolisted" name="flag_nolisted">一覧外</label>
        <label><input type="checkbox" id="flagNsfw" name="flag_nsfw">NSFW</label>
    </div>
    <div class="settings-info">
        画像変換：${{'0':'なし', '1':'webP', '2':'avif'}[config.imageConvertOption] ?? 'なし'}　サムネイル幅：${config.thumbnailWidth}px
        リサイズ制限：${config.resizeOption === '1' ? `有効(${config.maxDimension}px)` : '無効'}
        ファイル名：${{1:'元ファイル名', 2:'ランダム', 3:'日時+ユーザーID'}[config.fileNamingMode] ?? '元ファイル名'}
        最大枚数：${config.maxFileCount}枚　最大サイズ：${config.maxFileSizeMB}MB
    </div>
    <div id="overlay" style="display:none;"><div id="overlay-content">処理中...</div></div>
    `;

    targetElement.appendChild(uploaderContainer);
    uploaderContainer.style.display = 'block';

    const el = {
        dropArea:       document.getElementById('dropArea'),
        fileInput:      document.getElementById('fileInput'),
        cameraInput:    document.getElementById('cameraInput'),
        cameraBtn:      document.getElementById('cameraBtn'),
        previewArea:    document.getElementById('previewArea'),
        createThumbnail:document.getElementById('createThumbnail'),
        applyThumbnail: document.getElementById('applyThumbnail'),
        thumbnailOnlyTrim:document.getElementById('thumbnailOnlyTrim'),
        insertText:     document.getElementById('insertText'),
        customText:     document.getElementById('customText'),
        applyWatermark: document.getElementById('applyWatermark'),
        watermarkOptions:document.getElementById('watermarkOptions'),
        watermarkSelect:document.getElementById('watermarkSelect'),
        flagNolisted:   document.getElementById('flagNolisted'),
        flagNsfw:       document.getElementById('flagNsfw'),
        overlay:        document.getElementById('overlay'),
    };

    const allowedExtensions = ['jpg', 'jpeg', 'png', 'gif', 'webp', 'avif'];
    let selectedFiles = [];
    let selectedFileNames = new Set();
    let selectedDisplayNames = new Map(); // uploadName -> displayName（[PICT:]タグ表記名）
    let croppedFiles = new Map(); // uploadName -> トリミング済みFile（「サムネイルのみトリミング」用に元ファイルとは別に保持）

    const convertTargetExt = () => config.imageConvertOption === '1' ? 'webp' : config.imageConvertOption === '2' ? 'avif' : null;
    const isClipboard  = (name) => ['image.png','image.jpg','image.jpeg'].includes(name);
    // \p{L}はUnicodeの「文字」カテゴリ全般（日本語含む）にマッチするため、日本語ファイル名もそのまま使える
    const sanitizeName = (name) => name.replace(/[^\p{L}\p{N}\s\-_\.]/gu, '').replace(/ /g, '_');
    const randomStr    = (n) => Array.from(crypto.getRandomValues(new Uint8Array(n))).map(b => b.toString(36).padStart(2,'0')).join('').slice(0, n);
    const nowStr       = () => { const d = new Date(), p = (n) => String(n).padStart(2,'0'); return `${d.getFullYear()}${p(d.getMonth()+1)}${p(d.getDate())}${p(d.getHours())}${p(d.getMinutes())}${p(d.getSeconds())}`; };

    // ファイル名モードに応じてベース名を生成（拡張子なし）
    const generateBaseName = (originalName, index) => {
        const mode = parseInt(config.fileNamingMode ?? 1);
        if (mode === 2) return randomStr(12);
        if (mode === 3) return `${nowStr()}${String(index).padStart(2,'0')}-${config.userId ?? 'user'}`;
        // mode 1: 元ファイル名（クリップボード画像はランダム）
        if (isClipboard(originalName)) return `clipboard_${Date.now()}_${randomStr(6)}`;
        return sanitizeName(originalName.replace(/\.[^/.]+$/, ''));
    };

    function handleFiles(files) {
        const rejected = [], duplicates = [];

        Array.from(files).forEach((file, index) => {
            const ext = file.name.split('.').pop().toLowerCase();

            if (!allowedExtensions.includes(ext)) { rejected.push(file.name);  return; }

            const baseName   = generateBaseName(file.name, index);
            const uploadName = baseName + '.' + ext;           // PHPに渡す名前（元の拡張子）
            const displayName = baseName + '.' + (convertTargetExt() ?? ext); // タグに使う表示名

            if (selectedFileNames.has(uploadName)) { duplicates.push(file.name); return; }

            // PHPに送るFileオブジェクトは元の拡張子のままにする（PHP側でWebP変換する）
            const newFile = new File([file], uploadName, { type: file.type });
            selectedFiles.push(newFile);
            selectedFileNames.add(uploadName);
            selectedDisplayNames.set(uploadName, displayName);
            previewImage(file, uploadName, displayName);
            insertTextAtCursor(`[PICT:${displayName}]`);
        });

        if (rejected.length)   alert(`${rejected.join(', ')}は対応していないファイル形式です。`);
        if (duplicates.length) alert(`${duplicates.join(', ')}は既に選択されています。`);
    }

    function previewImage(file, fileName, displayName) {
        const originalFile = file; // 元画像を保持（再トリミング用）

        const container = document.createElement('div');
        container.style.cssText = 'display:inline-block;position:relative;margin:2px;text-align:center;vertical-align:top;';

        // FileReader(DataURL)の代わりにObjectURLを使用（メモリ効率が良い）
        let previewURL = URL.createObjectURL(file);
        const img = Object.assign(document.createElement('img'), { src: previewURL });
        img.dataset.fileName = fileName;
        img.style.cssText = 'display:block;max-height:100px;';

        const deleteBtn = Object.assign(document.createElement('button'), { textContent: '×' });
        deleteBtn.style.cssText = 'position:absolute;right:0;top:0;background-color:rgba(255,0,0,0.7);color:white;border:none;border-radius:50%;cursor:pointer;width:20px;height:20px;padding:0;';
        deleteBtn.onclick = (e) => {
            e.preventDefault();
            selectedFiles = selectedFiles.filter(f => f.name !== fileName);
            selectedFileNames.delete(fileName);
            selectedDisplayNames.delete(fileName);
            croppedFiles.delete(fileName);
            // ObjectURLを解放
            if (previewURL) { URL.revokeObjectURL(previewURL); previewURL = null; }
            img.src = '';
            container.remove();
            removeTagFromTextarea(`[PICT:${displayName}]`);
        };

        const trimBtn = Object.assign(document.createElement('button'), { textContent: 'トリミング' });
        trimBtn.style.cssText = 'display:block;width:100%;margin-top:2px;padding:2px 0;font-size:0.8em;background:rgba(0,120,255,0.8);color:white;border:none;cursor:pointer;border-radius:3px;';
        let lastCrop = null;
        trimBtn.onclick = (e) => {
            e.preventDefault();
            openCropModal(originalFile, fileName, (croppedFile, savedCrop) => {
                lastCrop = savedCrop;
                // 元ファイル(selectedFiles)はそのまま保持し、トリミング結果は別途保持する
                // （「サムネイルのみトリミング」時はアップロード画像側に反映させないため）
                croppedFiles.set(fileName, croppedFile);
                // 古いプレビューURLを解放して新しいURLをセット
                if (previewURL) URL.revokeObjectURL(previewURL);
                previewURL = URL.createObjectURL(croppedFile);
                img.src = previewURL;
            }, lastCrop);
        };

        container.append(img, trimBtn, deleteBtn);
        el.previewArea.appendChild(container);
    }

    // ── トリミングモーダル（canvas一枚描画方式）──────────────────────
    function openCropModal(file, fileName, onCrop, lastCrop = null) {
        const modal  = document.getElementById('cropModal');
        const canvas = document.getElementById('cropCanvas');
        const info   = document.getElementById('cropInfo');
        const ctx    = canvas.getContext('2d');

        const HANDLE = 24;
        let imgEl = null, scale = 1;
        let rotation = 0; // 0 / 90 / 180 / 270
        let sourceCanvas = null; // 回転を反映した実寸画像（描画・書き出しの両方でこれを参照する）
        let crop = null;
        let state = null;
        let dragHandle = null, startX, startY, startCrop;
        let rafId = null;

        const clamp  = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
        const toOrig = (v) => v / scale;

        // 現在の回転角度を反映した実寸canvasを作り直す
        function buildSourceCanvas() {
            if (!imgEl) return;
            // 回転のたびに古いcanvasを明示的に解放してから作り直す（GPUリソース解放）
            if (sourceCanvas) { sourceCanvas.width = sourceCanvas.height = 0; }
            const iw = imgEl.width, ih = imgEl.height;
            const swapped = rotation === 90 || rotation === 270;
            const oc = document.createElement('canvas');
            oc.width  = swapped ? ih : iw;
            oc.height = swapped ? iw : ih;
            const octx = oc.getContext('2d');
            octx.translate(oc.width / 2, oc.height / 2);
            octx.rotate(rotation * Math.PI / 180);
            octx.drawImage(imgEl, -iw / 2, -ih / 2);
            sourceCanvas = oc;
        }

        // sourceCanvasの実寸に合わせて表示用canvasのサイズ・縮尺を計算し直す
        function resizeCanvasToSource() {
            const maxW = Math.min(window.innerWidth  * 0.85, 800);
            const maxH = Math.min(window.innerHeight * 0.65, 600);
            scale = Math.min(maxW / sourceCanvas.width, maxH / sourceCanvas.height, 1);
            canvas.width  = Math.round(sourceCanvas.width  * scale);
            canvas.height = Math.round(sourceCanvas.height * scale);
        }

        function rotateBy(deg) {
            rotation = (rotation + deg + 360) % 360;
            buildSourceCanvas();
            resizeCanvasToSource();
            // 回転すると座標系が変わるため、選択範囲は全体にリセットする
            crop = { x: 0, y: 0, w: canvas.width, h: canvas.height };
            draw();
        }

        function draw() {
            if (!sourceCanvas) return;
            ctx.clearRect(0, 0, canvas.width, canvas.height);
            ctx.drawImage(sourceCanvas, 0, 0, canvas.width, canvas.height);
            if (!crop || crop.w < 1 || crop.h < 1) return;

            ctx.fillStyle = 'rgba(0,0,0,0.45)';
            ctx.fillRect(0, 0, canvas.width, crop.y);
            ctx.fillRect(0, crop.y, crop.x, crop.h);
            ctx.fillRect(crop.x + crop.w, crop.y, canvas.width - crop.x - crop.w, crop.h);
            ctx.fillRect(0, crop.y + crop.h, canvas.width, canvas.height - crop.y - crop.h);

            ctx.strokeStyle = '#fff';
            ctx.lineWidth = 2;
            ctx.strokeRect(crop.x, crop.y, crop.w, crop.h);

            const corners = [[crop.x, crop.y], [crop.x + crop.w, crop.y], [crop.x, crop.y + crop.h], [crop.x + crop.w, crop.y + crop.h]];
            ctx.fillStyle = '#fff';
            ctx.strokeStyle = '#555';
            ctx.lineWidth = 1.5;
            corners.forEach(([hx, hy]) => {
                ctx.beginPath();
                ctx.rect(hx - HANDLE/2, hy - HANDLE/2, HANDLE, HANDLE);
                ctx.fill(); ctx.stroke();
            });

            info.textContent = `選択範囲: ${Math.round(toOrig(crop.w))} × ${Math.round(toOrig(crop.h))} px`;
        }

        function hitHandle(px, py) {
            if (!crop) return null;
            for (const [key, hx, hy] of [
                ['nw', crop.x, crop.y], ['ne', crop.x + crop.w, crop.y],
                ['sw', crop.x, crop.y + crop.h], ['se', crop.x + crop.w, crop.y + crop.h],
            ]) {
                if (Math.abs(px - hx) <= HANDLE && Math.abs(py - hy) <= HANDLE) return key;
            }
            return null;
        }

        function hitInside(px, py) {
            return crop && crop.w > 1 && px > crop.x && px < crop.x + crop.w && py > crop.y && py < crop.y + crop.h;
        }

        function pos(e) {
            const r = canvas.getBoundingClientRect();
            const src = e.touches ? e.touches[0] : e;
            return { x: src.clientX - r.left, y: src.clientY - r.top };
        }

        function updateCursor(px, py) {
            const h = hitHandle(px, py);
            canvas.style.cursor = h ? ((h === 'nw' || h === 'se') ? 'nw-resize' : 'ne-resize')
                                 : hitInside(px, py) ? 'move' : 'crosshair';
        }

        function onStart(e) {
            e.preventDefault();
            const p = pos(e), h = hitHandle(p.x, p.y);
            startX = p.x; startY = p.y;
            if (h)                        { state = 'resize'; dragHandle = h; startCrop = { ...crop }; }
            else if (hitInside(p.x, p.y)) { state = 'move';                  startCrop = { ...crop }; }
            else                          { state = 'draw'; crop = { x: p.x, y: p.y, w: 0, h: 0 }; }
        }

        // canvas上のmousemoveでカーソル更新のみ（documentには登録しない）
        function onCanvasMove(e) {
            if (state) return; // ドラッグ中はdocument側のonDragMoveが担当
            const p = pos(e);
            updateCursor(p.x, p.y);
        }

        // ドラッグ中のみdocumentに登録するmove/upハンドラ
        function onDragMove(e) {
            if (!state) return;
            e.preventDefault();
            const p = pos(e), dx = p.x - startX, dy = p.y - startY;
            if (state === 'draw') {
                crop.x = clamp(Math.min(p.x, startX), 0, canvas.width);
                crop.y = clamp(Math.min(p.y, startY), 0, canvas.height);
                crop.w = clamp(Math.abs(p.x - startX), 0, canvas.width  - crop.x);
                crop.h = clamp(Math.abs(p.y - startY), 0, canvas.height - crop.y);
            } else if (state === 'move') {
                crop.x = clamp(startCrop.x + dx, 0, canvas.width  - startCrop.w);
                crop.y = clamp(startCrop.y + dy, 0, canvas.height - startCrop.h);
                crop.w = startCrop.w; crop.h = startCrop.h;
            } else if (state === 'resize') {
                let { x, y, w, h } = startCrop;
                if (dragHandle.includes('e')) w = clamp(w + dx, 10, canvas.width  - x);
                if (dragHandle.includes('s')) h = clamp(h + dy, 10, canvas.height - y);
                if (dragHandle.includes('w')) { const nw = clamp(w - dx, 10, x + w); x += w - nw; w = nw; }
                if (dragHandle.includes('n')) { const nh = clamp(h - dy, 10, y + h); y += h - nh; h = nh; }
                crop = { x, y, w, h };
            }
            if (rafId) cancelAnimationFrame(rafId);
            rafId = requestAnimationFrame(draw);
        }

        function onDragEnd() {
            state = null;
            document.removeEventListener('mousemove', onDragMove);
            document.removeEventListener('touchmove', onDragMove);
            document.removeEventListener('mouseup',   onDragEnd);
            document.removeEventListener('touchend',  onDragEnd);
        }

        // onStart でドラッグ開始時のみ document にリスナーを登録
        const _origOnStart = onStart;
        const onStartWrapped = (e) => {
            _origOnStart(e);
            if (state) {
                document.addEventListener('mousemove', onDragMove);
                document.addEventListener('touchmove', onDragMove, { passive: false });
                document.addEventListener('mouseup',   onDragEnd);
                document.addEventListener('touchend',  onDragEnd);
            }
        };

        canvas.addEventListener('mousedown',  onStartWrapped);
        canvas.addEventListener('touchstart', onStartWrapped, { passive: false });
        canvas.addEventListener('mousemove',  onCanvasMove);

        function cleanup() {
            canvas.removeEventListener('mousedown',  onStartWrapped);
            canvas.removeEventListener('touchstart', onStartWrapped);
            canvas.removeEventListener('mousemove',  onCanvasMove);
            document.removeEventListener('mousemove', onDragMove);
            document.removeEventListener('touchmove', onDragMove);
            document.removeEventListener('mouseup',   onDragEnd);
            document.removeEventListener('touchend',  onDragEnd);
            btnConfirm.removeEventListener('click', onConfirm);
            btnCancel.removeEventListener('click',  onCancel);
            btnRotateLeft.removeEventListener('click', onRotateLeft);
            btnRotateRight.removeEventListener('click', onRotateRight);
            if (rafId) cancelAnimationFrame(rafId);
            // Image解放
            if (imgEl) { imgEl.onload = null; imgEl.onerror = null; imgEl.src = ''; imgEl = null; }
            // canvasをクリア（GPUリソース解放）
            canvas.width = canvas.height = 0;
            if (sourceCanvas) { sourceCanvas.width = sourceCanvas.height = 0; sourceCanvas = null; }
        }

        const onConfirm = () => {
            if (!crop || crop.w < 2 || crop.h < 2) { info.textContent = '範囲が小さすぎます'; return; }
            const out = document.createElement('canvas');
            out.width  = Math.round(toOrig(crop.w));
            out.height = Math.round(toOrig(crop.h));
            out.getContext('2d').drawImage(sourceCanvas, toOrig(crop.x), toOrig(crop.y), toOrig(crop.w), toOrig(crop.h), 0, 0, out.width, out.height);
            const ext  = fileName.split('.').pop().toLowerCase();
            const mime = ext === 'png' ? 'image/png' : ext === 'webp' ? 'image/webp' : 'image/jpeg';
            // 書き出す実体（mime）と拡張子を必ず一致させる
            // （gif/avif等はJPEGにフォールバックするため、拡張子も.jpgに変更しないと
            //   サーバー側で「拡張子はavifなのに中身はJPEG」という不整合になりデコードに失敗する）
            const outExt = mime === 'image/png' ? 'png' : mime === 'image/webp' ? 'webp' : 'jpg';
            const outFileName = outExt === ext ? fileName : fileName.replace(/\.[^.]+$/, '') + '.' + outExt;
            const savedCrop = { x: toOrig(crop.x), y: toOrig(crop.y), w: toOrig(crop.w), h: toOrig(crop.h) };
            out.toBlob(blob => {
                out.width = out.height = 0; // GPU解放
                cleanup();
                modal.classList.remove('active');
                onCrop(new File([blob], outFileName, { type: mime }), savedCrop);
            }, mime);
        };
        const onCancel      = () => { cleanup(); modal.classList.remove('active'); };
        const onRotateLeft  = () => rotateBy(-90);
        const onRotateRight = () => rotateBy(90);

        const btnConfirm     = document.getElementById('cropConfirm');
        const btnCancel      = document.getElementById('cropCancel');
        const btnRotateLeft  = document.getElementById('cropRotateLeft');
        const btnRotateRight = document.getElementById('cropRotateRight');
        btnConfirm.addEventListener('click', onConfirm);
        btnCancel.addEventListener('click',  onCancel);
        btnRotateLeft.addEventListener('click',  onRotateLeft);
        btnRotateRight.addEventListener('click', onRotateRight);

        const objectURL = URL.createObjectURL(file);
        imgEl = new Image();
        imgEl.onload = () => {
            URL.revokeObjectURL(objectURL);
            rotation = 0;
            buildSourceCanvas();
            resizeCanvasToSource();
            crop = lastCrop
                ? { x: clamp(lastCrop.x * scale, 0, canvas.width),  y: clamp(lastCrop.y * scale, 0, canvas.height),
                    w: clamp(lastCrop.w * scale, 1, canvas.width),   h: clamp(lastCrop.h * scale, 1, canvas.height) }
                : { x: 0, y: 0, w: canvas.width, h: canvas.height };
            draw();
            modal.classList.add('active');
        };
        imgEl.src = objectURL;
    }

    const tegalogTA = document.querySelector('.tegalogpost');

    function insertTextAtCursor(text) {
        if (!tegalogTA) return;
        const s = tegalogTA.selectionStart, e = tegalogTA.selectionEnd;
        tegalogTA.value = tegalogTA.value.slice(0, s) + text + tegalogTA.value.slice(e);
        tegalogTA.selectionStart = tegalogTA.selectionEnd = s + text.length;
    }

    function removeTagFromTextarea(tag) {
        if (tegalogTA) tegalogTA.value = tegalogTA.value.replace(tag, '');
    }

    // イベントリスナー
    el.createThumbnail.addEventListener('change', () => {
        if (!el.createThumbnail.checked) {
            el.applyThumbnail.checked = false;
            el.thumbnailOnlyTrim.checked = false;
        }
    });
    el.applyThumbnail.addEventListener('change', () => {
        if (el.applyThumbnail.checked) el.createThumbnail.checked = true;
    });
    el.thumbnailOnlyTrim.addEventListener('change', () => {
        if (el.thumbnailOnlyTrim.checked) el.createThumbnail.checked = true;
    });
    el.applyWatermark.addEventListener('change', () => {
        el.watermarkOptions.style.display = el.applyWatermark.checked ? 'block' : 'none';
        el.watermarkSelect.disabled = !el.applyWatermark.checked;
    });
    document.addEventListener('paste', (e) => {
        const files = Array.from(e.clipboardData.items)
            .filter(i => i.kind === 'file' && i.type.startsWith('image/'))
            .map(i => i.getAsFile());
        if (files.length) handleFiles(files);
    });
    el.dropArea.addEventListener('dragover',  (e) => { e.preventDefault(); el.dropArea.style.borderColor = 'green'; });
    el.dropArea.addEventListener('dragleave', ()  => { el.dropArea.style.borderColor = '#ccc'; });
    el.dropArea.addEventListener('drop',      (e) => { e.preventDefault(); el.dropArea.style.borderColor = '#ccc'; handleFiles(e.dataTransfer.files); });
    el.dropArea.addEventListener('click',     ()  => el.fileInput.click());
    el.fileInput.addEventListener('change',   (e) => handleFiles(e.target.files));
    el.cameraBtn.addEventListener('click',    ()  => el.cameraInput.click());
    el.cameraInput.addEventListener('change', (e) => { handleFiles(e.target.files); e.target.value = ''; });

    document.querySelector('form.postform').addEventListener('submit', async function(e) {
        e.preventDefault();
        if (!e.submitter?.classList.contains('postbutton')) return;
        if (selectedFiles.length === 0) { this.submit(); return; }

        el.overlay.style.display = 'flex';
        try {
            const formData = new FormData();
            selectedFiles.forEach((file, i) => {
                const cropped = croppedFiles.get(file.name);
                // 「サムネイルのみトリミング」がオフなら従来通りトリミング結果をそのままアップロードに使う。
                // オンの場合はアップロード画像は元のまま送り、トリミング結果は別フィールドでサムネイル生成用に送る
                const useThumbOnly = !!cropped && el.thumbnailOnlyTrim.checked;
                const uploadFile   = (cropped && !useThumbOnly) ? cropped : file;

                formData.append('files[]', uploadFile);
                formData.append('fileNames[]', uploadFile.name);
                formData.append('displayNames[]', selectedDisplayNames.get(file.name) ?? uploadFile.name);
                if (useThumbOnly) {
                    formData.append(`thumbSourceFiles[${i}]`, cropped, uploadFile.name);
                }
            });
            formData.append('createThumbnail', el.createThumbnail.checked ? 'yes' : 'no');
            formData.append('applyThumbnail',  el.applyThumbnail.checked  ? '1'   : '0');
            if (el.insertText.checked) {
                formData.append('insertText', 'yes');
                formData.append('customText', el.customText.value);
            }
            if (el.applyWatermark.checked) {
                formData.append('applyWatermark',    'yes');
                formData.append('watermarkPosition', document.querySelector('input[name="watermarkPosition"]:checked').value);
                formData.append('watermarkFile',     el.watermarkSelect.value);
            }

            const response = await fetch('tega-exuploader/uploader.php', { method: 'POST', body: formData });
            const rawBody  = await response.text();
            let data;
            try {
                data = JSON.parse(rawBody);
            } catch (parseErr) {
                console.error('[tega-exuploader] uploader.phpの応答がJSONではありません', { status: response.status, statusText: response.statusText, body: rawBody });
                throw new Error(`サーバーから予期しない応答がありました(HTTP ${response.status})。詳細はブラウザのコンソールを確認してください。`);
            }
            if (!response.ok || data.error) {
                console.error('[tega-exuploader] uploader.phpでエラーが発生しました', { status: response.status, data });
                throw new Error(data.error || `アップロードに失敗しました(HTTP ${response.status})`);
            }

            // サーバー側で実際に保存されたファイル名が異なる場合（衝突時のみ）、本文中のタグを置き換える
            for (const [oldName, newName] of Object.entries(data.renames ?? {})) {
                if (oldName === newName) continue;
                tegalogTA.value = tegalogTA.value.split(`[PICT:${oldName}]`).join(`[PICT:${newName}]`);
            }

            // 拡張アップローダーはimages/index.xmlを直接更新しないため、
            // てがろぐ本体の画像アップロード処理(mode=imageup)をファイル無しで叩き、
            // 本体側の自動インデックス更新(loadImageIndex)を代わりに走らせる
            if (data.files && data.files.length) {
                try {
                    const reindexRes = await fetch(location.href, {
                        method: 'POST',
                        credentials: 'same-origin',
                        body: new URLSearchParams({ mode: 'imageup' }),
                    });
                    if (!reindexRes.ok) {
                        console.error('[tega-exuploader] 画像インデックスの再構築リクエストが失敗しました', { status: reindexRes.status, statusText: reindexRes.statusText });
                    }

                    // 一覧外／NSFWフラグが指定されていれば、インデックス登録された各画像にフラグを反映する
                    if (el.flagNolisted.checked || el.flagNsfw.checked) {
                        for (const fileName of data.files) {
                            const metaParams = new URLSearchParams({
                                mode: 'admin',
                                work: 'updateimgmeta',
                                img: fileName,
                            });
                            if (el.flagNolisted.checked) metaParams.set('flag_nolisted', '1');
                            if (el.flagNsfw.checked)     metaParams.set('flag_nsfw', '1');
                            const metaRes = await fetch(location.href, {
                                method: 'POST',
                                credentials: 'same-origin',
                                body: metaParams,
                            });
                            if (!metaRes.ok) {
                                console.error(`[tega-exuploader] 画像フラグの更新に失敗しました (${fileName})`, { status: metaRes.status, statusText: metaRes.statusText });
                            }
                        }
                    }
                } catch (e) {
                    console.error('[tega-exuploader] 画像インデックスの更新に失敗しました:', e);
                }
            }

            this.submit();
        } catch (err) {
            el.overlay.style.display = 'none';
            alert('画像処理に失敗しました: ' + err.message);
            console.error('[tega-exuploader] Upload error:', err);
        }
    });
});
