/**
 * qix_creator.js  — オリジナルステージ作成・シェア (縦スマホ向け キャンバスUI)
 */

// ─── 敵タイプ定義 ────────────────────────────────────────────────────────
const CREATOR_TYPES = {
    QIX:         { ja:'跳ね返り', code:'Q',  col:'#00CC55', icon:'◆', hasCol:true,  hasSp:true,  defSpd:4.0, extra:[] },
    TRACKER:     { ja:'追跡体',   code:'T',  col:'#FF3355', icon:'◎', hasCol:false, hasSp:true,  defSpd:0.8, extra:[] },
    GHOST:       { ja:'幽霊',     code:'G',  col:'#6666CC', icon:'❋', hasCol:true,  hasSp:true,  defSpd:1.2, extra:[] },
    SHOOTER:     { ja:'射撃体',   code:'S',  col:'#FF8800', icon:'⊕', hasCol:true,  hasSp:true,  defSpd:4.0, extra:['interval','offset'] },
    SNAKE:       { ja:'免疫体',   code:'N',  col:'#22AA44', icon:'≋', hasCol:true,  hasSp:true,  defSpd:4.0, extra:['bodyLength','turnRate'] },
    MINE:        { ja:'地雷',     code:'M',  col:'#CC6600', icon:'✸', hasCol:true,  hasSp:false, defSpd:0,   extra:['triggerRadius'] },
    BEAM_BOSS:   { ja:'ビーム×4', code:'B',  col:'#CC3300', icon:'⚡', hasCol:true,  hasSp:true,  defSpd:4.0, extra:['moveDuration','chargeDuration','fireDuration','beamWidth','offset'] },
    SNIPER_BEAM: { ja:'狙撃ビーム',code:'SB',col:'#FF0088', icon:'⊛', hasCol:true,  hasSp:true,  defSpd:4.0, extra:['moveDuration','chargeDuration','fireDuration','beamWidth','offset'] },
    OCTA_BEAM:   { ja:'ビーム×8', code:'OB', col:'#FF6600', icon:'✦', hasCol:true,  hasSp:true,  defSpd:4.0, extra:['moveDuration','chargeDuration','fireDuration','beamWidth','offset'] },
};

const EXTRA_DEF = {
    interval:60,offset:0,bodyLength:20,turnRate:70,triggerRadius:22,
    moveDuration:180,chargeDuration:70,fireDuration:22,beamWidth:14,
};
const EXTRA_JA = {
    interval:'発射間隔',offset:'位相',bodyLength:'体長',turnRate:'旋回率',triggerRadius:'感知半径',
    moveDuration:'移動時間',chargeDuration:'チャージ',fireDuration:'発射時間',beamWidth:'幅',
};
const EXTRA_RNG = {
    interval:[30,500],offset:[0,300],bodyLength:[5,80],turnRate:[30,120],
    triggerRadius:[8,60],moveDuration:[60,400],chargeDuration:[20,200],
    fireDuration:[8,80],beamWidth:[4,30],
};

// ─── LS キー ─────────────────────────────────────────────────────────────
const LS_SLOTS = 'qix_creator_slots'; // [{id:101-200, name, encoded}]

// ─── クリエイタークラス ──────────────────────────────────────────────────
class StageCreator {
    constructor(game) {
        this.game = game;
        this._ov  = null;  // overlay element
        this._cv  = null;  // canvas element
        this._ctx = null;
        this._scale = 1;
        this._raf   = null;
        this._palType = 'QIX';  // 現在選択中のパレット種別
        // ドラッグ状態
        this._drag = null; // {mode:'enemy'|'palette', idx, startX, startY, moved}
        this._selIdx = -1; // 選択中の敵インデックス
        // スロット選択UI
        this._showSlots = false;
        this.reset();
    }

    // ─── データリセット ───────────────────────────────────────────────────
    reset() {
        this.name   = 'MY STAGE';
        this.quota  = 0.75;
        this.alpha  = 0.65;
        this.enemies = [];
        this.sparks  = { count:2, speed:4.0, color:'#FF0055' };
        this._selIdx = -1;
        this._palType = 'QIX';
        this._showSlots = false;
    }

    // ─── 開閉 ─────────────────────────────────────────────────────────────
    open() {
        let ov = document.getElementById('creator-overlay');
        if (!ov) {
            ov = document.createElement('div');
            ov.id = 'creator-overlay';
            document.body.appendChild(ov);
        }
        this._ov = ov;
        ov.innerHTML = this._html();
        ov.style.display = 'flex';
        this._initCanvas();
        this._bindAll();
        this._renderDetail();
        this._startLoop();
    }

    close() {
        this._stopLoop();
        if (this._ov) this._ov.style.display = 'none';
        this.game.state = 'TITLE';
        document.getElementById('title-overlay').style.display = 'flex';
    }

    returnFromPlay() {
        this._ov.style.display = 'flex';
        this._startLoop();
    }

    // ─── HTML骨格 ─────────────────────────────────────────────────────────
    _html() {
        return `
<div class="cr2-wrap">
  <div class="cr2-header">
    <button class="cr2-hbtn" id="cr2-close">✕</button>
    <input class="cr2-nameinput" id="cr2-name" maxlength="20" value="${this._esc(this.name)}" placeholder="ステージ名">
    <button class="cr2-hbtn cr2-savebtn" id="cr2-save">💾 保存</button>
  </div>

  <div class="cr2-main-row">
    <div class="cr2-canvas-wrap">
      <canvas id="cr2-cv"></canvas>
      <div class="cr2-cv-hint" id="cr2-hint">↑ ドラッグして配置</div>
    </div>
    <div class="cr2-elist-wrap">
      <div class="cr2-elist-hd">敵 <span id="cr2-cnt">${this.enemies.length}/100</span></div>
      <div class="cr2-elist" id="cr2-elist">${this._elistHTML()}</div>
    </div>
  </div>

  <div class="cr2-palette-wrap">
    <div class="cr2-palette" id="cr2-palette">
      ${Object.entries(CREATOR_TYPES).map(([k,t]) => `
        <div class="cr2-chip${k===this._palType?' cr2-chip-sel':''}" data-type="${k}">
          <span class="cr2-chip-icon" style="color:${t.col}">${t.icon}</span>
          <span class="cr2-chip-ja">${t.ja}</span>
        </div>`).join('')}
    </div>
  </div>

  <div class="cr2-params">
    <div class="cr2-pm"><div class="cr2-pm-lbl">クリア率</div><div class="cr2-pm-ctrl"><button class="cr2-pmbtn" id="cr2-q-dn">▼</button><span class="cr2-pm-val" id="cr2-qv">${Math.round(this.quota*100)}%</span><button class="cr2-pmbtn" id="cr2-q-up">▲</button></div></div>
    <div class="cr2-pm"><div class="cr2-pm-lbl">重ね画像</div><div class="cr2-pm-ctrl"><button class="cr2-pmbtn" id="cr2-a-dn">▼</button><span class="cr2-pm-val" id="cr2-av">${Math.round(this.alpha*100)}%</span><button class="cr2-pmbtn" id="cr2-a-up">▲</button></div></div>
    <div class="cr2-pm"><div class="cr2-pm-lbl">SP数</div><div class="cr2-pm-ctrl"><button class="cr2-pmbtn" id="cr2-sc-dn">▼</button><span class="cr2-pm-val" id="cr2-scv">${this.sparks.count}</span><button class="cr2-pmbtn" id="cr2-sc-up">▲</button></div></div>
    <div class="cr2-pm"><div class="cr2-pm-lbl">SP速度</div><div class="cr2-pm-ctrl"><button class="cr2-pmbtn" id="cr2-ss-dn">▼</button><span class="cr2-pm-val" id="cr2-ssv">${this.sparks.speed.toFixed(1)}</span><button class="cr2-pmbtn" id="cr2-ss-up">▲</button></div></div>
    <div class="cr2-pm"><div class="cr2-pm-lbl">SP色</div><input type="color" class="cr2-pm-color" id="cr2-scolor" value="${this.sparks.color}"></div>
  </div>

  <div class="cr2-detail-panel" id="cr2-detail"></div>

  <div class="cr2-footer">
    <button class="cr2-fbtn cr2-fbtn-play"   id="cr2-play">▶ プレイ</button>
    <button class="cr2-fbtn cr2-fbtn-load"   id="cr2-load">📂 読込</button>
    <button class="cr2-fbtn cr2-fbtn-copy"   id="cr2-copy">📋 コード</button>
    <button class="cr2-fbtn cr2-fbtn-import" id="cr2-import">📥 貼付</button>
  </div>

  <div class="cr2-slot-sheet" id="cr2-slot-sheet" style="display:none">
    <div class="cr2-slot-hd">
      <span>フリープレイに保存 (スロット選択)</span>
      <button class="cr2-hbtn" id="cr2-slot-close">✕</button>
    </div>
    <div class="cr2-slot-grid" id="cr2-slot-grid"></div>
  </div>

  <div class="cr2-slot-sheet" id="cr2-load-sheet" style="display:none">
    <div class="cr2-slot-hd">
      <span>フリーステージを読み込む</span>
      <button class="cr2-hbtn" id="cr2-load-close">✕</button>
    </div>
    <div class="cr2-load-list" id="cr2-load-list"></div>
  </div>
</div>`;
    }

    // ─── 敵リスト (右列) ─────────────────────────────────────────────────
    _elistHTML() {
        if (this.enemies.length === 0)
            return '<div class="cr2-elist-empty">敵なし</div>';
        const NUMS = '①②③④⑤⑥⑦⑧⑨⑩';
        return this.enemies.map((e, i) => {
            const def = CREATOR_TYPES[e.type];
            const sel = i === this._selIdx;
            const total = this.enemies.filter(x => x.type === e.type).length;
            const n     = this.enemies.slice(0, i + 1).filter(x => x.type === e.type).length - 1;
            const label = def.ja + (total > 1 ? (NUMS[n] ?? String(n + 1)) : '');
            return `<div class="cr2-ecard${sel ? ' cr2-ecard-sel' : ''}" data-idx="${i}">
  <span class="cr2-ecard-icon" style="color:${def.col}">${def.icon}</span>
  <span class="cr2-ecard-ja">${label}</span>
  <button class="cr2-ecard-del" data-idx="${i}">✕</button>
</div>`;
        }).join('');
    }

    _renderEList() {
        const el  = document.getElementById('cr2-elist');
        const cnt = document.getElementById('cr2-cnt');
        if (el)  el.innerHTML = this._elistHTML();
        if (cnt) cnt.textContent = `${this.enemies.length}/100`;
        this._bindEList();
    }

    _bindEList() {
        const el = document.getElementById('cr2-elist');
        if (!el) return;
        el.querySelectorAll('.cr2-ecard').forEach(card => {
            card.addEventListener('pointerdown', ev => {
                if (ev.target.classList.contains('cr2-ecard-del')) return;
                ev.stopPropagation();
                const idx = parseInt(card.dataset.idx);
                this._selIdx = idx;
                this._renderEList();
                this._renderDetail();
            });
        });
        el.querySelectorAll('.cr2-ecard-del').forEach(btn => {
            btn.addEventListener('pointerdown', ev => {
                ev.stopPropagation();
                const idx = parseInt(btn.dataset.idx);
                this.enemies.splice(idx, 1);
                if (this._selIdx >= this.enemies.length) this._selIdx = this.enemies.length - 1;
                this._renderEList();
                if (this._selIdx >= 0) this._renderDetail();
                else this._hideDetail();
            });
        });
    }

    // ─── キャンバス初期化 & ループ ────────────────────────────────────────
    _initCanvas() {
        this._cv  = document.getElementById('cr2-cv');
        this._ctx = this._cv.getContext('2d');
        this._resizeCanvas();
    }

    _resizeCanvas() {
        const wrap = this._cv.parentElement;
        const w = wrap.clientWidth, h = wrap.clientHeight || wrap.clientWidth;
        this._cv.width  = w;
        this._cv.height = h;
        const s = Math.min(w, h);
        this._scale = s / 600;
        this._offX = Math.floor((w - s) / 2);
        this._offY = Math.floor((h - s) / 2);
    }

    _startLoop() {
        this._stopLoop();
        const loop = () => {
            this._draw();
            this._raf = requestAnimationFrame(loop);
        };
        this._raf = requestAnimationFrame(loop);
    }

    _stopLoop() {
        if (this._raf) { cancelAnimationFrame(this._raf); this._raf = null; }
    }

    // ─── 描画 ─────────────────────────────────────────────────────────────
    _draw() {
        const ctx = this._ctx, s = this._scale;
        const W = this._cv.width, H = this._cv.height;
        const ox = this._offX || 0, oy = this._offY || 0;
        const F = 600 * s;

        ctx.clearRect(0, 0, W, H);
        ctx.fillStyle = '#050005';
        ctx.fillRect(0, 0, W, H);

        ctx.fillStyle = '#000';
        ctx.fillRect(ox, oy, F, F);

        ctx.strokeStyle = 'rgba(80,0,120,0.3)';
        ctx.lineWidth = 0.5;
        for (let i = 0; i <= 6; i++) {
            const p = i * 100 * s;
            ctx.beginPath(); ctx.moveTo(ox+p, oy); ctx.lineTo(ox+p, oy+F); ctx.stroke();
            ctx.beginPath(); ctx.moveTo(ox, oy+p); ctx.lineTo(ox+F, oy+p); ctx.stroke();
        }

        ctx.strokeStyle = '#440044';
        ctx.lineWidth = 2;
        ctx.strokeRect(ox+1, oy+1, F-2, F-2);

        this.enemies.forEach((e, i) => this._drawEnemy(ctx, e, i, s, ox, oy));

        if (this._drag && this._drag.mode === 'palette' && this._drag.cvX != null) {
            this._drawGhost(ctx, this._drag.type, ox + this._drag.cvX * s, oy + this._drag.cvY * s, s);
        }
    }

    _drawEnemy(ctx, e, idx, s, ox, oy) {
        const def = CREATOR_TYPES[e.type];
        const x = (ox||0) + e.x * s, y = (oy||0) + e.y * s;
        const r = 14 * s;
        const isSelected = idx === this._selIdx;

        // 選択リング
        if (isSelected) {
            ctx.beginPath();
            ctx.arc(x, y, r + 4 * s, 0, Math.PI * 2);
            ctx.strokeStyle = '#fff';
            ctx.lineWidth = 2;
            ctx.stroke();
            ctx.beginPath();
            ctx.arc(x, y, r + 2 * s, 0, Math.PI * 2);
            ctx.strokeStyle = def.col;
            ctx.lineWidth = 2;
            ctx.stroke();
        }

        // 本体円
        ctx.beginPath();
        ctx.arc(x, y, r, 0, Math.PI * 2);
        ctx.fillStyle = def.col + '44';
        ctx.fill();
        ctx.strokeStyle = def.col;
        ctx.lineWidth = isSelected ? 2 : 1.5;
        ctx.stroke();

        // アイコン
        ctx.fillStyle = def.col;
        ctx.font = `${Math.round(12 * s)}px sans-serif`;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(def.icon, x, y);
    }

    _drawGhost(ctx, type, px, py, s) {
        const def = CREATOR_TYPES[type];
        const r = 14 * s;
        ctx.globalAlpha = 0.6;
        ctx.beginPath();
        ctx.arc(px, py, r, 0, Math.PI * 2);
        ctx.fillStyle = def.col + '44';
        ctx.fill();
        ctx.strokeStyle = def.col;
        ctx.lineWidth = 1.5;
        ctx.stroke();
        ctx.fillStyle = def.col;
        ctx.font = `${Math.round(12 * s)}px sans-serif`;
        ctx.textAlign = 'center';
        ctx.textBaseline = 'middle';
        ctx.fillText(def.icon, px, py);
        ctx.globalAlpha = 1;
    }

    // ─── イベント ─────────────────────────────────────────────────────────
    _bindAll() {
        const $ = id => document.getElementById(id);

        $('cr2-close').onclick  = () => this.close();
        $('cr2-name').oninput   = e => { this.name = e.target.value; };
        $('cr2-save').onclick   = () => this._openSlotSheet();
        $('cr2-play').onclick   = () => this.play();
        $('cr2-load').onclick   = () => this._openLoadSheet();
        $('cr2-copy').onclick   = () => this.copyCode();
        $('cr2-import').onclick = () => this.importDialog();
        $('cr2-slot-close').onclick = () => this._closeSlotSheet();
        $('cr2-load-close').onclick = () => this._closeLoadSheet();

        // ─ ステージパラメーター (+/−ボタン) ─
        const updn = (idDn, idUp, idVal, get, set, min, max, step, fmt) => {
            $(idDn).onclick = () => { set(Math.max(min, +(get()-step).toFixed(4))); $(idVal).innerText = fmt(get()); };
            $(idUp).onclick = () => { set(Math.min(max, +(get()+step).toFixed(4))); $(idVal).innerText = fmt(get()); };
        };
        updn('cr2-q-dn','cr2-q-up','cr2-qv', ()=>this.quota,  v=>{this.quota=v;},  0.50,1.00,0.05, v=>`${Math.round(v*100)}%`);
        updn('cr2-a-dn','cr2-a-up','cr2-av', ()=>this.alpha,  v=>{this.alpha=v;},  0.00,1.00,0.05, v=>`${Math.round(v*100)}%`);
        updn('cr2-sc-dn','cr2-sc-up','cr2-scv', ()=>this.sparks.count, v=>{this.sparks.count=Math.round(v);}, 0,10,1, v=>`${Math.round(v)}`);
        updn('cr2-ss-dn','cr2-ss-up','cr2-ssv', ()=>this.sparks.speed, v=>{this.sparks.speed=v;},  1.0,12.0,0.5, v=>v.toFixed(1));
        $('cr2-scolor').oninput = e => { this.sparks.color = e.target.value; };

        // ─ 敵リスト ─
        this._bindEList();

        // ─ パレット ─
        document.querySelectorAll('.cr2-chip').forEach(chip => {
            const type = chip.dataset.type;
            chip.addEventListener('pointerdown', ev => {
                ev.preventDefault();
                this._palType = type;
                document.querySelectorAll('.cr2-chip').forEach(c => c.classList.toggle('cr2-chip-sel', c.dataset.type === type));
                this._drag = { mode:'palette', type, cvX:null, cvY:null };
                chip.setPointerCapture(ev.pointerId);
                const hint = $('cr2-hint');
                if (hint) hint.style.display = 'none';
            });
            chip.addEventListener('pointermove', ev => {
                if (!this._drag || this._drag.mode !== 'palette') return;
                const cvRect = this._cv.getBoundingClientRect();
                const rx = ev.clientX - cvRect.left;
                const ry = ev.clientY - cvRect.top;
                if (rx >= 0 && rx <= cvRect.width && ry >= 0 && ry <= cvRect.height) {
                    this._drag.cvX = rx / this._scale;
                    this._drag.cvY = ry / this._scale;
                } else {
                    this._drag.cvX = null;
                }
            });
            chip.addEventListener('pointerup', ev => {
                if (!this._drag || this._drag.mode !== 'palette') return;
                const cvRect = this._cv.getBoundingClientRect();
                const rx = ev.clientX - cvRect.left;
                const ry = ev.clientY - cvRect.top;
                if (rx >= 0 && rx <= cvRect.width && ry >= 0 && ry <= cvRect.height) {
                    this._placeEnemy(type, Math.round(rx / this._scale), Math.round(ry / this._scale));
                } else {
                    const p = this._findFreePos();
                    this._placeEnemy(type, p.x, p.y);
                }
                this._drag = null;
            });
        });

        // ─ キャンバス操作 ─
        const cv = this._cv;
        cv.addEventListener('pointerdown', ev => {
            ev.preventDefault();
            const pos = this._cvPos(ev);
            const hit = this._hitTest(pos.x, pos.y);
            if (hit >= 0) {
                this._selIdx = hit;
                this._drag = { mode:'enemy', idx:hit, startX:pos.x, startY:pos.y, moved:false };
                cv.setPointerCapture(ev.pointerId);
                this._renderEList();
                this._renderDetail();
            } else {
                if (this._drag) return;
                this._selIdx = -1;
                this._renderEList();
                this._hideDetail();
            }
        });

        cv.addEventListener('pointermove', ev => {
            if (!this._drag || this._drag.mode !== 'enemy') return;
            const pos = this._cvPos(ev);
            const dx = pos.x - this._drag.startX;
            const dy = pos.y - this._drag.startY;
            if (!this._drag.moved && Math.sqrt(dx*dx+dy*dy) > 8) this._drag.moved = true;
            if (this._drag.moved) {
                this.enemies[this._drag.idx].x = Math.max(0, Math.min(600, Math.round(pos.x)));
                this.enemies[this._drag.idx].y = Math.max(0, Math.min(600, Math.round(pos.y)));
                this._updateDetailXY();
            }
        });

        cv.addEventListener('pointerup', () => { this._drag = null; });

        window.addEventListener('resize', () => { this._resizeCanvas(); });
    }

    _cvPos(ev) {
        const rect = this._cv.getBoundingClientRect();
        const ox = this._offX || 0, oy = this._offY || 0;
        return {
            x: (ev.clientX - rect.left - ox) / this._scale,
            y: (ev.clientY - rect.top  - oy) / this._scale,
        };
    }

    _hitTest(gx, gy) {
        const R = 20; // タッチ半径(game座標)
        for (let i = this.enemies.length - 1; i >= 0; i--) {
            const e = this.enemies[i];
            if ((e.x-gx)**2 + (e.y-gy)**2 <= R*R) return i;
        }
        return -1;
    }

    // ─── クリック配置時の空き座標探索 ────────────────────────────────────
    _findFreePos() {
        if (this.enemies.length === 0) return { x: 300, y: 300 };
        const MIN_D = 20;
        const candidates = [];
        for (let x = 50; x <= 550; x += 50)
            for (let y = 50; y <= 550; y += 50)
                candidates.push([x, y]);
        candidates.sort((a, b) => Math.hypot(a[0]-300, a[1]-300) - Math.hypot(b[0]-300, b[1]-300));
        for (const [cx, cy] of candidates) {
            if (this.enemies.every(e => Math.hypot(e.x - cx, e.y - cy) >= MIN_D))
                return { x: cx, y: cy };
        }
        return { x: 300, y: 300 };
    }

    // ─── 敵配置 ───────────────────────────────────────────────────────────
    _placeEnemy(type, x, y) {
        if (this.enemies.length >= 100) { this._toast('上限100体です'); return; }
        const def = CREATOR_TYPES[type];
        const e = { type, x, y };
        if (def.hasCol) e.color = def.col;
        if (def.hasSp)  e.speed = def.defSpd;
        def.extra.forEach(k => { e[k] = EXTRA_DEF[k]; });
        this.enemies.push(e);
        this._selIdx = this.enemies.length - 1;
        this._renderEList();
        this._renderDetail();
        const hint = document.getElementById('cr2-hint');
        if (hint) hint.style.display = 'none';
    }

    _deleteSelected() {
        if (this._selIdx < 0) return;
        this.enemies.splice(this._selIdx, 1);
        this._selIdx = -1;
        this._renderEList();
        this._hideDetail();
    }

    // ─── 詳細パネル ───────────────────────────────────────────────────────
    _renderDetail() {
        const panel = document.getElementById('cr2-detail');
        if (!panel) return;
        if (this._selIdx < 0 || this._selIdx >= this.enemies.length) {
            panel.innerHTML = '<div class="cr2-det-empty">← 敵をタップして編集</div>';
            return;
        }
        const e = this.enemies[this._selIdx];
        const def = CREATOR_TYPES[e.type];
        const idx = this._selIdx;

        const pmItem = (id, lbl, valId, val, fmt) =>
            `<div class="cr2-dm-item"><div class="cr2-dm-lbl">${lbl}</div><div class="cr2-dm-ctrl"><button class="cr2-dmbtn" id="${id}-dn">▼</button><span class="cr2-dm-val" id="${valId}">${fmt(val)}</span><button class="cr2-dmbtn" id="${id}-up">▲</button></div></div>`;

        const ghost = () => '<div class="cr2-dm-item cr2-dm-ghost"><div class="cr2-dm-lbl">-</div><div class="cr2-dm-ctrl"><span class="cr2-dm-val">-</span></div></div>';
        const padTo5 = items => { const a = [...items]; while (a.length % 5) a.push(ghost()); return a.join(''); };

        const row1 = padTo5([
            pmItem('cr2-dx', 'X', 'cr2-dv-x', e.x, v => Math.round(v)),
            pmItem('cr2-dy', 'Y', 'cr2-dv-y', e.y, v => Math.round(v)),
            def.hasSp  ? pmItem('cr2-dsp', '速度', 'cr2-dv-sp', e.speed ?? def.defSpd, v => v.toFixed(1)) : ghost(),
            def.hasCol ? `<div class="cr2-dm-item"><div class="cr2-dm-lbl">色</div><input type="color" class="cr2-dm-color" id="cr2-det-col" value="${e.color||def.col}"></div>` : ghost(),
        ]);

        const extraItems = def.extra.map(k => {
            const v = e[k] ?? EXTRA_DEF[k];
            return pmItem(`cr2-dk-${k}`, EXTRA_JA[k], `cr2-dv-${k}`, v, v => v);
        });
        const row2 = extraItems.length > 0 ? padTo5(extraItems) : '';

        panel.innerHTML = `
<div class="cr2-det-head">
  <span class="cr2-det-type" style="color:${def.col}">${def.icon} ${def.ja}</span>
  <button class="cr2-det-del" id="cr2-del">削除</button>
</div>
<div class="cr2-dm-row">${row1}</div>
${row2 ? `<div class="cr2-dm-row">${row2}</div>` : ''}`;
        panel.style.display = 'block';

        document.getElementById('cr2-del').onclick = () => this._deleteSelected();

        const bindUD = (id, get, set, mn, mx, step, fmt, valId) => {
            const dn = document.getElementById(id+'-dn');
            const up = document.getElementById(id+'-up');
            const vEl = () => document.getElementById(valId);
            if (dn) dn.onclick = () => { set(Math.max(mn, +(get()-step).toFixed(4))); if(vEl()) vEl().innerText = fmt(get()); };
            if (up) up.onclick = () => { set(Math.min(mx, +(get()+step).toFixed(4))); if(vEl()) vEl().innerText = fmt(get()); };
        };
        bindUD('cr2-dx',  ()=>e.x, v=>{this.enemies[idx].x=v;},  0,600,5, v=>Math.round(v), 'cr2-dv-x');
        bindUD('cr2-dy',  ()=>e.y, v=>{this.enemies[idx].y=v;},  0,600,5, v=>Math.round(v), 'cr2-dv-y');
        if (def.hasSp) {
            bindUD('cr2-dsp', ()=>e.speed??def.defSpd, v=>{this.enemies[idx].speed=v;}, 0.1,20,0.1, v=>v.toFixed(1), 'cr2-dv-sp');
        }
        if (def.hasCol) {
            const col = document.getElementById('cr2-det-col');
            if (col) col.oninput = ev => { this.enemies[idx].color = ev.target.value; };
        }
        def.extra.forEach(k => {
            const [mn, mx] = EXTRA_RNG[k] || [0,9999];
            const step = Math.max(1, Math.round((mx-mn)/50));
            bindUD(`cr2-dk-${k}`, ()=>e[k]??EXTRA_DEF[k], v=>{this.enemies[idx][k]=Math.round(v);}, mn,mx,step, v=>v, `cr2-dv-${k}`);
        });
    }

    _updateDetailXY() {
        if (this._selIdx < 0) return;
        const e = this.enemies[this._selIdx];
        const dx = document.getElementById('cr2-dv-x');
        const dy = document.getElementById('cr2-dv-y');
        if (dx) dx.innerText = Math.round(e.x);
        if (dy) dy.innerText = Math.round(e.y);
    }

    _hideDetail() {
        const panel = document.getElementById('cr2-detail');
        if (panel) { panel.innerHTML = '<div class="cr2-det-empty">← 敵をタップして編集</div>'; }
    }

    // ─── スロット保存シート ────────────────────────────────────────────────
    _openSlotSheet() {
        const slots = this._loadSlots();
        const grid  = document.getElementById('cr2-slot-grid');
        if (!grid) return;

        grid.innerHTML = '';
        for (let id = 101; id <= 200; id++) {
            const slot  = slots.find(s => s.id === id);
            const cell  = document.createElement('div');
            cell.className = 'cr2-slot-cell' + (slot ? ' cr2-slot-taken' : ' cr2-slot-empty');
            cell.innerHTML = `<div class="cr2-slot-id">${id}</div><div class="cr2-slot-name">${slot ? this._esc(slot.name) : '(空き)'}</div>`;
            cell.onclick = () => {
                if (slot && !confirm(`スロット ${id}「${slot.name}」を上書きしますか？`)) return;
                this._saveToSlot(id);
                this._closeSlotSheet();
            };
            grid.appendChild(cell);
        }
        document.getElementById('cr2-slot-sheet').style.display = 'flex';
    }

    _closeSlotSheet() {
        document.getElementById('cr2-slot-sheet').style.display = 'none';
    }

    _openLoadSheet() {
        const list = document.getElementById('cr2-load-list');
        if (!list) return;
        list.innerHTML = '';
        const stages = STAGE_DATA.FREE;
        stages.forEach(stage => {
            if (!stage.enemies || stage.enemies.length === 0) return;
            const label = stage._stageName
                ? `#${stage.id} ${stage._stageName}`
                : stage._isCreator
                    ? `#${stage.id} MY STAGE`
                    : `TARGET ${stage.id}`;
            const row = document.createElement('div');
            row.className = 'cr2-load-row';
            row.innerHTML = `
                <span class="cr2-load-label">${this._esc(label)}</span>
                <span class="cr2-load-meta">${stage.enemies.length}体 / 目標${Math.round(stage.clearQuota*100)}%</span>`;
            row.onclick = () => { this._loadFromStage(stage); this._closeLoadSheet(); };
            list.appendChild(row);
        });
        document.getElementById('cr2-load-sheet').style.display = 'flex';
    }

    _closeLoadSheet() {
        document.getElementById('cr2-load-sheet').style.display = 'none';
    }

    _loadFromStage(stage) {
        const validTypes = new Set(Object.keys(CREATOR_TYPES));
        this.enemies = stage.enemies
            .filter(e => validTypes.has(e.type))
            .map(e => ({ ...e }))
            .slice(0, 100);
        this.quota  = stage.clearQuota  || 0.75;
        this.alpha  = stage.overlayAlpha != null ? stage.overlayAlpha : 0.65;
        this.sparks = stage.sparks ? { ...stage.sparks } : { count:2, speed:4.0, color:'#FF0055' };
        this._selIdx = -1;
        this._ov.innerHTML = this._html();
        this._initCanvas();
        this._bindAll();
        this._startLoop();
        this._toast(`ステージ読み込み完了 (${this.enemies.length}体)`);
    }

    _saveToSlot(id) {
        const slots = this._loadSlots().filter(s => s.id !== id);
        slots.push({ id, name: this.name, encoded: this.encodeStage() });
        slots.sort((a,b) => a.id - b.id);
        try {
            localStorage.setItem(LS_SLOTS, JSON.stringify(slots));
            this._toast(`スロット ${id} に保存しました`);
            StageCreator.mergeCreatorSlots();
            if (this.game.achievements) this.game.achievements.onCreatorSave();
        } catch(e) {
            this._toast('保存失敗');
        }
    }

    _loadSlots() {
        try {
            return JSON.parse(localStorage.getItem(LS_SLOTS) || '[]');
        } catch(_) { return []; }
    }

    static loadAllSlots() {
        try {
            return JSON.parse(localStorage.getItem(LS_SLOTS) || '[]');
        } catch(_) { return []; }
    }

    static mergeCreatorSlots() {
        // STAGE_DATA.FREE の101-200番台を更新
        const slots  = StageCreator.loadAllSlots();
        const base   = STAGE_DATA.FREE.filter(s => typeof s.id === 'number' && s.id <= 100);

        // バリアントディレクトリを既存ステージから自動検出
        // 例: "images/f/stage001_hidden.jpg" → "images/f/"
        //     "images/stage001_hidden.jpg"   → "images/"
        const sampleStage = base.find(s => s.assets && s.assets.hidden);
        const variantDir  = (() => {
            if (!sampleStage) return 'images/f/';
            const m = sampleStage.assets.hidden.match(/^(images\/(?:\w+\/)?)stage/);
            return m ? m[1] : 'images/';
        })();

        const custom = slots.map(s => {
            const dec = StageCreator.decodeStatic(s.encoded);
            if (!dec) return null;
            const idStr = String(s.id).padStart(3, '0');
            return {
                id: s.id, difficulty: 1,
                clearQuota:   dec.quota,
                overlayAlpha: dec.alpha,
                assets: {
                    hidden: variantDir + 'stage' + idStr + '_hidden.jpg',
                    reveal: variantDir + 'stage' + idStr + '_reveal.jpg',
                },
                enemies: dec.enemies,
                sparks:  dec.sparks,
                _stageName: dec.name,
                _isCreator: true,
            };
        }).filter(Boolean);
        STAGE_DATA.FREE = [...base, ...custom];
    }

    // ─── プレイ ───────────────────────────────────────────────────────────
    play() {
        if (this.enemies.length === 0) { alert('敵を最低1体配置してください'); return; }
        const stageData = {
            id:'custom', difficulty:1,
            clearQuota: this.quota, overlayAlpha: this.alpha,
            assets: { hidden:null, reveal:null },
            enemies: this.enemies.map(e => ({...e})),
            sparks:  {...this.sparks},
            _stageName: this.name,
        };
        this._stopLoop();
        this._ov.style.display = 'none';
        this.game.loadCustomStage(stageData);
    }

    // ─── エンコード / デコード ────────────────────────────────────────────
    encodeStage() {
        const enc = this.enemies.map(e => {
            const def = CREATOR_TYPES[e.type];
            const a = [def.code, Math.round(e.x), Math.round(e.y)];
            if (def.hasCol) a.push((e.color||'#000').replace('#',''));
            if (def.hasSp)  a.push(Math.round((e.speed||1)*10));
            def.extra.forEach(k => a.push(e[k]??EXTRA_DEF[k]));
            return a;
        });
        const d = {
            v:1, n:this.name,
            q:Math.round(this.quota*100),
            a:Math.round(this.alpha*100),
            e:enc,
            k:[this.sparks.count, Math.round(this.sparks.speed*10), (this.sparks.color||'#FF0055').replace('#','')]
        };
        return btoa(unescape(encodeURIComponent(JSON.stringify(d))))
            .replace(/\+/g,'-').replace(/\//g,'_').replace(/=/g,'');
    }

    static decodeStatic(str) {
        try {
            const b64 = str.replace(/-/g,'+').replace(/_/g,'/');
            const pad = b64 + '=='.slice(0,(4-b64.length%4)%4);
            const d   = JSON.parse(decodeURIComponent(escape(atob(pad))));
            if (d.v !== 1) return null;
            const enemies = (d.e||[]).map(arr => {
                const code = arr[0];
                const entry = Object.entries(CREATOR_TYPES).find(([,df]) => df.code === code);
                if (!entry) return null;
                const [type, def] = entry;
                let i = 1;
                const e = {type, x:arr[i++], y:arr[i++]};
                if (def.hasCol) e.color = '#'+arr[i++];
                if (def.hasSp)  e.speed = arr[i++]/10;
                def.extra.forEach(k => { e[k] = arr[i++]??EXTRA_DEF[k]; });
                return e;
            }).filter(Boolean);
            return {
                name: d.n||'CUSTOM', quota:(d.q||75)/100, alpha:(d.a||65)/100,
                enemies,
                sparks:{count:d.k[0], speed:d.k[1]/10, color:'#'+d.k[2]}
            };
        } catch(_) { return null; }
    }

    decodeStage(str) { return StageCreator.decodeStatic(str); }

    // ─── コピー / 読込 ────────────────────────────────────────────────────
    copyCode() {
        const code = this.encodeStage();
        if (navigator.clipboard) {
            navigator.clipboard.writeText(code).then(() => this._toast('コードをコピーしました'));
        } else {
            prompt('コードをコピーしてください:', code);
        }
    }

    importDialog() {
        const code = prompt('ステージコードを貼り付けてください:');
        if (!code) return;
        const data = this.decodeStage(code.trim());
        if (!data) { alert('無効なコードです'); return; }
        Object.assign(this, { name:data.name, quota:data.quota, alpha:data.alpha, enemies:data.enemies, sparks:data.sparks });
        this._selIdx = -1;
        // 再描画
        this._ov.innerHTML = this._html();
        this._initCanvas();
        this._bindAll();
        this._startLoop();
        this._toast('読み込みました');
    }

    // ─── トースト ─────────────────────────────────────────────────────────
    _toast(msg) {
        const t = document.createElement('div');
        t.textContent = msg;
        t.style.cssText='position:fixed;top:20px;left:50%;transform:translateX(-50%);background:#BC13FE;color:#fff;padding:10px 24px;border-radius:20px;font-family:"New Rocker",cursive;font-size:16px;z-index:9999;pointer-events:none;transition:opacity 0.5s;white-space:nowrap;';
        document.body.appendChild(t);
        setTimeout(()=>{t.style.opacity='0';},1500);
        setTimeout(()=>t.remove(),2100);
    }

    _esc(s) { return String(s).replace(/&/g,'&amp;').replace(/"/g,'&quot;').replace(/</g,'&lt;'); }
}
