/* TOKEN FORGE — SPL Token Launcher
   Deploys custom SPL tokens on Solana mainnet with Metaplex metadata.
   Manual instruction encoding for SPL Token + Metaplex programs.
   Requires @solana/web3.js CDN (solanaWeb3 global).
*/
(function () {
    'use strict';

    // ── Constants ──────────────────────────────────────────────
    const RPC_URL = 'https://api.mainnet-beta.solana.com';
    const FEE_WALLET = '9NuiHh5wgRPx69BFGP1ZR8kHiBENGoJrXs5GpZzKAyn8';
    const SERVICE_FEE_LAMPORTS = 100_000_000; // 0.1 SOL
    const MINT_ACCOUNT_SIZE = 82;

    const TOKEN_PROGRAM   = new solanaWeb3.PublicKey('TokenkegQfeZyiNwAJbNbGKPFXCWuBvf9Ss623VQ5DA');
    const ATA_PROGRAM     = new solanaWeb3.PublicKey('ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL');
    const METADATA_PROGRAM = new solanaWeb3.PublicKey('metaqbxxUEFHGOqbxLZq71kbiKTfnLRjnNt8GYJoMKM');
    const SYSTEM_PROGRAM  = solanaWeb3.SystemProgram.programId;
    const RENT_SYSVAR     = new solanaWeb3.PublicKey('SysvarRent111111111111111111111111111111111');

    const SOLSCAN_TX    = 'https://solscan.io/tx/';
    const SOLSCAN_TOKEN = 'https://solscan.io/token/';
    const EXPLORER_TX   = 'https://explorer.solana.com/tx/';
    const MAX_U64       = 18446744073709551615n;

    // ── State ──────────────────────────────────────────────────
    let walletAddress = null;

    // ── DOM ────────────────────────────────────────────────────
    const $ = s => document.querySelector(s);

    document.addEventListener('DOMContentLoaded', () => {
        initWallet();
        initForm();
    });

    // ═══════════════════════════════════════════════════════════
    //  WALLET
    // ═══════════════════════════════════════════════════════════

    function initWallet() {
        window.addEventListener('wallet-connected', e => {
            walletAddress = e.detail.address;
            updateWalletUI();
        });
        window.addEventListener('wallet-disconnected', () => {
            walletAddress = null;
            updateWalletUI();
        });
        if (window.solWallet?.connected) walletAddress = window.solWallet.address;
        updateWalletUI();

        const btn = $('#wallet-btn');
        if (btn) btn.addEventListener('click', () => {
            walletAddress ? window.solWallet.disconnect() : showWalletPicker();
        });
    }

    function updateWalletUI() {
        const s = $('#wallet-status'), b = $('#wallet-btn');
        if (!s || !b) return;
        if (walletAddress) {
            s.className = 'tf-wallet-status connected';
            s.textContent = '\u25CF CONNECTED \u2014 ' + truncAddr(walletAddress);
            b.className = 'tf-wallet-btn disconnect';
            b.textContent = 'DISCONNECT';
        } else {
            s.className = 'tf-wallet-status';
            s.textContent = '\u25CB NOT CONNECTED';
            b.className = 'tf-wallet-btn';
            b.textContent = 'CONNECT WALLET';
        }
        updateDeployBtn();
    }

    function showWalletPicker() {
        const wallets = window.solWallet.getAvailableWallets();
        const known = window.solWallet.KNOWN_WALLETS;
        let m = $('#tf-wallet-modal');
        if (!m) {
            m = document.createElement('div');
            m.id = 'tf-wallet-modal';
            m.className = 'tf-modal';
            document.body.appendChild(m);
        }

        const names = new Set(wallets.map(w => w.name));
        const det = wallets.map(w =>
            '<button class="tf-wallet-option" data-w="' + esc(w.name) + '">' + w.icon + ' ' + esc(w.name) + '</button>'
        ).join('');
        const inst = known.filter(k => !names.has(k.name)).map(k =>
            '<a href="' + esc(k.url) + '" target="_blank" rel="noopener" class="tf-wallet-install">' + k.icon + ' ' + esc(k.name) + ' \u2014 INSTALL \u2197</a>'
        ).join('');

        m.innerHTML =
            '<div class="tf-modal-backdrop"></div>' +
            '<div class="tf-modal-content">' +
            '<div class="tf-modal-title">SELECT WALLET</div>' +
            (det
                ? '<div style="display:flex;flex-direction:column;gap:0.5rem;margin-bottom:1rem">' + det + '</div>'
                : '<div style="font-family:JetBrains Mono,monospace;font-size:0.6rem;color:rgba(255,255,255,0.3);margin-bottom:1rem">NO WALLETS DETECTED</div>') +
            (inst
                ? '<div style="font-family:JetBrains Mono,monospace;font-size:0.55rem;color:rgba(255,255,255,0.25);letter-spacing:1px;margin-bottom:0.5rem">INSTALL A WALLET</div><div style="display:flex;flex-direction:column;gap:0.35rem">' + inst + '</div>'
                : '') +
            '</div>';

        m.classList.add('active');
        m.querySelector('.tf-modal-backdrop').onclick = () => m.classList.remove('active');
        m.querySelectorAll('.tf-wallet-option').forEach(btn => {
            btn.onclick = async () => {
                m.classList.remove('active');
                try { await window.solWallet.connect(btn.dataset.w); } catch (e) {
                    console.warn('[TokenForge] Connect failed:', e.message);
                }
            };
        });
    }

    // ═══════════════════════════════════════════════════════════
    //  FORM
    // ═══════════════════════════════════════════════════════════

    function initForm() {
        $('#deploy-btn')?.addEventListener('click', handleDeploy);
        document.querySelectorAll('.tf-input,.tf-textarea,.tf-select,#revoke-mint,#revoke-freeze').forEach(el => {
            el.addEventListener('input', updateDeployBtn);
            el.addEventListener('change', updateDeployBtn);
        });
    }

    function formData() {
        return {
            name:         ($('#token-name')?.value || '').trim(),
            symbol:       ($('#token-symbol')?.value || '').trim().toUpperCase(),
            supply:       ($('#token-supply')?.value || '').trim(),
            decimals:     parseInt($('#token-decimals')?.value || '6', 10),
            imageUrl:     ($('#token-image')?.value || '').trim(),
            desc:         ($('#token-desc')?.value || '').trim(),
            revokeMint:   !!$('#revoke-mint')?.checked,
            revokeFreeze: !!$('#revoke-freeze')?.checked,
        };
    }

    function validate() {
        const d = formData(), e = [];
        if (!d.name || d.name.length > 32)   e.push('Token name required (max 32 chars)');
        if (!d.symbol || d.symbol.length > 10) e.push('Symbol required (max 10 chars)');

        // Supply validation
        const supNum = Number(d.supply);
        if (!d.supply || !Number.isFinite(supNum) || supNum <= 0 || supNum !== Math.floor(supNum)) {
            e.push('Supply must be a positive whole number');
        } else {
            try {
                const raw = BigInt(Math.floor(supNum)) * (10n ** BigInt(d.decimals));
                if (raw > MAX_U64) e.push('Supply \u00D7 10^decimals exceeds u64 max');
            } catch (_) { e.push('Invalid supply number'); }
        }
        if (d.decimals < 0 || d.decimals > 9) e.push('Decimals must be 0-9');
        return e;
    }

    function updateDeployBtn() {
        const b = $('#deploy-btn');
        if (b) b.disabled = validate().length > 0 || !walletAddress;
    }

    // ═══════════════════════════════════════════════════════════
    //  DEPLOY FLOW
    // ═══════════════════════════════════════════════════════════

    function handleDeploy() {
        const errs = validate();
        if (errs.length) { showStatus('error', 'VALIDATION ERROR', errs.join('<br>')); return; }
        if (!walletAddress || !window.solWallet?.provider) {
            showStatus('error', 'WALLET NOT CONNECTED', 'Connect your wallet first.');
            return;
        }
        showConfirmModal(formData());
    }

    function showConfirmModal(d) {
        let m = $('#tf-confirm-modal');
        if (!m) {
            m = document.createElement('div');
            m.id = 'tf-confirm-modal';
            m.className = 'tf-modal';
            document.body.appendChild(m);
        }
        const supFmt = Number(d.supply).toLocaleString();
        const hasRevoke = d.revokeMint || d.revokeFreeze;

        m.innerHTML =
            '<div class="tf-modal-backdrop"></div>' +
            '<div class="tf-modal-content">' +
            '<div class="tf-modal-title">CONFIRM TOKEN DEPLOYMENT</div>' +
            mRow('NAME', esc(d.name)) +
            mRow('SYMBOL', esc(d.symbol)) +
            mRow('SUPPLY', supFmt) +
            mRow('DECIMALS', String(d.decimals)) +
            (d.imageUrl ? mRow('IMAGE', '<span style="font-size:0.5rem">' + esc(d.imageUrl.length > 50 ? d.imageUrl.slice(0, 47) + '...' : d.imageUrl) + '</span>') : '') +
            (d.revokeMint  ? mRow('REVOKE MINT',   '<span style="color:#F5A623">YES \u2014 PERMANENT</span>') : '') +
            (d.revokeFreeze ? mRow('REVOKE FREEZE', '<span style="color:#F5A623">YES \u2014 PERMANENT</span>') : '') +
            mRow('DEPLOYER', truncAddr(walletAddress)) +
            '<div style="margin-top:1rem;padding-top:.75rem;border-top:1px solid rgba(245,166,35,.15)">' +
                mRow('SERVICE FEE', '0.1 SOL') +
                mRow('NETWORK FEES', '~0.015 SOL') +
                mRow('ESTIMATED TOTAL', '<span style="color:#F5A623;font-weight:700">~0.115 SOL</span>') +
            '</div>' +
            (hasRevoke ? '<div class="tf-modal-warning">\u26A0 AUTHORITY REVOCATION IS PERMANENT AND CANNOT BE UNDONE.' +
                (d.revokeMint ? ' You will not be able to mint additional tokens.' : '') +
                (d.revokeFreeze ? ' You will not be able to freeze accounts.' : '') + '</div>' : '') +
            '<div class="tf-modal-warning">\u26A0 THIS IS A REAL ON-CHAIN TRANSACTION. SOL WILL BE DEDUCTED FROM YOUR WALLET. VERIFY ALL DETAILS BEFORE CONFIRMING.</div>' +
            '<div class="tf-modal-actions">' +
                '<button class="tf-modal-cancel" id="confirm-cancel">CANCEL</button>' +
                '<button class="tf-modal-confirm" id="confirm-deploy">DEPLOY TOKEN</button>' +
            '</div>' +
            '</div>';

        m.classList.add('active');
        m.querySelector('.tf-modal-backdrop').onclick = () => m.classList.remove('active');
        m.querySelector('#confirm-cancel').onclick    = () => m.classList.remove('active');
        m.querySelector('#confirm-deploy').onclick    = () => { m.classList.remove('active'); executeDeploy(d); };
    }

    async function executeDeploy(data) {
        try {
            showStatus('building', 'BUILDING TRANSACTION', 'Generating mint keypair and constructing instructions...');
            disableForm(true);

            const conn      = new solanaWeb3.Connection(RPC_URL, 'confirmed');
            const payer     = new solanaWeb3.PublicKey(walletAddress);
            const feeWallet = new solanaWeb3.PublicKey(FEE_WALLET);
            const mintKp    = solanaWeb3.Keypair.generate();
            const mint      = mintKp.publicKey;

            // Rent exemption for mint account
            const mintRent = await conn.getMinimumBalanceForRentExemption(MINT_ACCOUNT_SIZE);

            // Derive Associated Token Account address
            const [ata] = solanaWeb3.PublicKey.findProgramAddressSync(
                [payer.toBytes(), TOKEN_PROGRAM.toBytes(), mint.toBytes()],
                ATA_PROGRAM
            );

            // Derive Metaplex metadata PDA
            const [metaPDA] = solanaWeb3.PublicKey.findProgramAddressSync(
                [new TextEncoder().encode('metadata'), METADATA_PROGRAM.toBytes(), mint.toBytes()],
                METADATA_PROGRAM
            );

            // Calculate raw supply with decimals
            const rawSupply = BigInt(Math.floor(Number(data.supply))) * (10n ** BigInt(data.decimals));

            // ── Build instruction list ──
            const ixs = [];

            // 1. Create mint account (SystemProgram)
            ixs.push(solanaWeb3.SystemProgram.createAccount({
                fromPubkey: payer,
                newAccountPubkey: mint,
                space: MINT_ACCOUNT_SIZE,
                lamports: mintRent,
                programId: TOKEN_PROGRAM,
            }));

            // 2. InitializeMint2 — SPL Token instruction 20
            ixs.push(ixInitMint2(mint, data.decimals, payer, payer));

            // 3. Create Associated Token Account (idempotent)
            ixs.push(ixCreateATA(payer, ata, payer, mint));

            // 4. MintTo — mint initial supply to user's ATA
            ixs.push(ixMintTo(mint, ata, payer, rawSupply));

            // 5. Create Metaplex metadata
            ixs.push(ixCreateMetadata(metaPDA, mint, payer, data));

            // 6. Revoke mint authority (optional)
            if (data.revokeMint) ixs.push(ixSetAuthority(mint, payer, 0, null));

            // 7. Revoke freeze authority (optional)
            if (data.revokeFreeze) ixs.push(ixSetAuthority(mint, payer, 1, null));

            // 8. Service fee transfer — 0.1 SOL to site wallet
            ixs.push(solanaWeb3.SystemProgram.transfer({
                fromPubkey: payer,
                toPubkey: feeWallet,
                lamports: SERVICE_FEE_LAMPORTS,
            }));

            // ── Build transaction ──
            const { blockhash, lastValidBlockHeight } = await conn.getLatestBlockhash('confirmed');
            const tx = new solanaWeb3.Transaction();
            tx.recentBlockhash = blockhash;
            tx.feePayer = payer;
            ixs.forEach(ix => tx.add(ix));

            // Partial sign with the mint keypair (payer signs via wallet)
            tx.partialSign(mintKp);

            // ── Sign & send ──
            showStatus('awaiting', 'AWAITING WALLET SIGNATURE', 'Please approve the transaction in your wallet...');

            let sig;
            if (window.solWallet.isWalletStandard) {
                sig = await wsSend(tx, conn);
            } else {
                sig = await legacySend(tx, conn);
            }

            // ── Confirm on-chain ──
            showStatus('confirming', 'CONFIRMING ON-CHAIN',
                'Transaction submitted \u2014 awaiting confirmation...<br>' +
                rRow('TX', '<a href="' + SOLSCAN_TX + sig + '" target="_blank" rel="noopener">' + truncAddr(sig) + ' \u2197</a>')
            );

            const conf = await conn.confirmTransaction(
                { signature: sig, blockhash, lastValidBlockHeight },
                'confirmed'
            );
            if (conf.value.err) throw new Error('On-chain failure: ' + JSON.stringify(conf.value.err));

            // ── Success ──
            const mintAddr = mint.toBase58();
            showStatus('success', data.symbol + ' DEPLOYED SUCCESSFULLY',
                rRow('TOKEN', esc(data.name) + ' (' + esc(data.symbol) + ')') +
                rRow('SUPPLY', Number(data.supply).toLocaleString()) +
                rRow('DECIMALS', String(data.decimals)) +
                rRow('MINT ADDRESS', '<span style="font-size:0.55rem">' + mintAddr + '</span>') +
                rRow('SOLSCAN', '<a href="' + SOLSCAN_TOKEN + mintAddr + '" target="_blank" rel="noopener">View Token \u2197</a>') +
                rRow('EXPLORER', '<a href="' + EXPLORER_TX + sig + '" target="_blank" rel="noopener">View Transaction \u2197</a>') +
                rRow('TX SIGNATURE', '<a href="' + SOLSCAN_TX + sig + '" target="_blank" rel="noopener" style="font-size:0.5rem">' + truncAddr(sig) + ' \u2197</a>')
            );

        } catch (err) {
            console.error('[TokenForge] Deploy error:', err);
            let msg = err.message || 'Unknown error';
            if (/reject|denied|cancel|declined|disapproved/i.test(msg))
                msg = 'Transaction was rejected by your wallet.';
            else if (/insufficient|not enough|0x1/i.test(msg))
                msg = 'Insufficient SOL balance. You need approximately 0.115 SOL.';
            else if (/blockhash|expired|block height/i.test(msg))
                msg = 'Transaction expired. Please try again.';
            else if (/network|fetch|failed to fetch/i.test(msg))
                msg = 'Network error. Check your connection and try again.';
            showStatus('error', 'DEPLOYMENT FAILED', esc(msg));
        } finally {
            disableForm(false);
        }
    }

    // ═══════════════════════════════════════════════════════════
    //  SIGN & SEND HELPERS
    // ═══════════════════════════════════════════════════════════

    async function legacySend(tx, conn) {
        const p = window.solWallet.provider;

        // Try signAndSendTransaction first (Phantom, Solflare, etc.)
        if (typeof p.signAndSendTransaction === 'function') {
            try {
                const r = await p.signAndSendTransaction(tx, {
                    skipPreflight: false,
                    preflightCommitment: 'confirmed',
                });
                return r.signature || r;
            } catch (e) {
                if (/reject|denied|cancel|declined|disapproved/i.test(e.message || '')) throw e;
                console.warn('[TokenForge] signAndSendTransaction failed, fallback:', e.message);
            }
        }

        // Fallback: signTransaction + manual send
        if (typeof p.signTransaction !== 'function')
            throw new Error('Wallet does not support transaction signing.');
        const signed = await p.signTransaction(tx);
        return await conn.sendRawTransaction(signed.serialize(), {
            skipPreflight: false,
            preflightCommitment: 'confirmed',
        });
    }

    async function wsSend(tx, conn) {
        const w = window.solWallet.provider;
        const acct = w.accounts?.[0];
        if (!acct) throw new Error('No wallet account available.');

        const bytes = tx.serialize({ requireAllSignatures: false, verifySignatures: false });

        // Try signAndSendTransaction feature
        const feat = w.features?.['solana:signAndSendTransaction'];
        if (feat) {
            try {
                const res = await feat.signAndSendTransaction({
                    account: acct,
                    transaction: bytes,
                    chain: 'solana:mainnet',
                });
                const r = Array.isArray(res) ? res[0] : res;
                if (r.signature) {
                    return typeof r.signature === 'string' ? r.signature : b58(new Uint8Array(r.signature));
                }
                return r;
            } catch (e) {
                if (/reject|denied|cancel|declined|disapproved/i.test(e.message || '')) throw e;
                console.warn('[TokenForge] WS signAndSend failed, fallback:', e.message);
            }
        }

        // Fallback: signTransaction + manual send
        const sf = w.features?.['solana:signTransaction'];
        if (!sf) throw new Error('Wallet does not support Solana signing.');
        const res = await sf.signTransaction({
            account: acct,
            transaction: bytes,
            chain: 'solana:mainnet',
        });
        const r = Array.isArray(res) ? res[0] : res;
        return await conn.sendRawTransaction(r.signedTransaction, {
            skipPreflight: false,
            preflightCommitment: 'confirmed',
        });
    }

    // ═══════════════════════════════════════════════════════════
    //  SPL TOKEN INSTRUCTIONS (manual binary encoding)
    // ═══════════════════════════════════════════════════════════

    /**
     * InitializeMint2 — SPL Token instruction index 20
     * Data: [20, decimals(u8), mintAuthority(32), freezeOption(u8), freezeAuthority(32)]
     * Accounts: [mint(writable)]
     */
    function ixInitMint2(mint, decimals, mintAuth, freezeAuth) {
        const d = new Uint8Array(67);
        d[0] = 20;
        d[1] = decimals;
        d.set(mintAuth.toBytes(), 2);
        d[34] = 1; // COption::Some
        d.set(freezeAuth.toBytes(), 35);
        return new solanaWeb3.TransactionInstruction({
            programId: TOKEN_PROGRAM,
            keys: [{ pubkey: mint, isSigner: false, isWritable: true }],
            data: d,
        });
    }

    /**
     * MintTo — SPL Token instruction index 7
     * Data: [7, amount(u64 LE)]
     * Accounts: [mint(writable), destination(writable), authority(signer)]
     */
    function ixMintTo(mint, dest, auth, amount) {
        const d = new Uint8Array(9);
        d[0] = 7;
        writeU64(d, amount, 1);
        return new solanaWeb3.TransactionInstruction({
            programId: TOKEN_PROGRAM,
            keys: [
                { pubkey: mint, isSigner: false, isWritable: true },
                { pubkey: dest, isSigner: false, isWritable: true },
                { pubkey: auth, isSigner: true,  isWritable: false },
            ],
            data: d,
        });
    }

    /**
     * SetAuthority — SPL Token instruction index 6
     * Data: [6, authorityType(u8), newAuthorityOption(u8), newAuthority?(32)]
     * authorityType: 0=MintTokens, 1=FreezeAccount
     * Accounts: [account(writable), currentAuthority(signer)]
     */
    function ixSetAuthority(acct, curAuth, authType, newAuth) {
        const hasNew = newAuth !== null;
        const d = new Uint8Array(hasNew ? 35 : 3);
        d[0] = 6;
        d[1] = authType;
        d[2] = hasNew ? 1 : 0;
        if (hasNew) d.set(newAuth.toBytes(), 3);
        return new solanaWeb3.TransactionInstruction({
            programId: TOKEN_PROGRAM,
            keys: [
                { pubkey: acct,    isSigner: false, isWritable: true },
                { pubkey: curAuth, isSigner: true,  isWritable: false },
            ],
            data: d,
        });
    }

    /**
     * CreateIdempotent — Associated Token Account Program instruction 1
     * Data: [1]
     * Accounts: [payer(s,w), ata(w), owner, mint, systemProgram, tokenProgram]
     */
    function ixCreateATA(payer, ata, owner, mint) {
        return new solanaWeb3.TransactionInstruction({
            programId: ATA_PROGRAM,
            keys: [
                { pubkey: payer,          isSigner: true,  isWritable: true  },
                { pubkey: ata,            isSigner: false, isWritable: true  },
                { pubkey: owner,          isSigner: false, isWritable: false },
                { pubkey: mint,           isSigner: false, isWritable: false },
                { pubkey: SYSTEM_PROGRAM, isSigner: false, isWritable: false },
                { pubkey: TOKEN_PROGRAM,  isSigner: false, isWritable: false },
            ],
            data: new Uint8Array([1]),
        });
    }

    // ═══════════════════════════════════════════════════════════
    //  METAPLEX TOKEN METADATA INSTRUCTION
    // ═══════════════════════════════════════════════════════════

    /**
     * CreateMetadataAccountV3 — Metaplex instruction index 33
     * Borsh-serialized: [33, DataV2(...), isMutable(bool), collectionDetails(Option)]
     */
    function ixCreateMetadata(metaPDA, mint, authority, tokenData) {
        const enc  = new TextEncoder();
        const nameB = enc.encode(tokenData.name);
        const symB  = enc.encode(tokenData.symbol);
        const uriB  = enc.encode(tokenData.imageUrl || '');

        // Layout: 1(ix) + string(name) + string(symbol) + string(uri) + u16 + opt + opt + opt + bool + opt
        // Borsh string = 4-byte u32 LE length prefix + UTF-8 bytes
        const sz = 1
            + (4 + nameB.length)
            + (4 + symB.length)
            + (4 + uriB.length)
            + 2   // seller_fee_basis_points
            + 1   // creators: None
            + 1   // collection: None
            + 1   // uses: None
            + 1   // is_mutable
            + 1;  // collection_details: None

        const d = new Uint8Array(sz);
        let o = 0;

        d[o++] = 33; // instruction index

        // DataV2.name
        writeU32(d, nameB.length, o); o += 4;
        d.set(nameB, o); o += nameB.length;

        // DataV2.symbol
        writeU32(d, symB.length, o); o += 4;
        d.set(symB, o); o += symB.length;

        // DataV2.uri
        writeU32(d, uriB.length, o); o += 4;
        d.set(uriB, o); o += uriB.length;

        // DataV2.seller_fee_basis_points (u16 LE) — 0 for fungible tokens
        d[o++] = 0; d[o++] = 0;

        // DataV2.creators: None
        d[o++] = 0;
        // DataV2.collection: None
        d[o++] = 0;
        // DataV2.uses: None
        d[o++] = 0;

        // is_mutable: true
        d[o++] = 1;

        // collection_details: None
        d[o++] = 0;

        return new solanaWeb3.TransactionInstruction({
            programId: METADATA_PROGRAM,
            keys: [
                { pubkey: metaPDA,        isSigner: false, isWritable: true  }, // metadata PDA
                { pubkey: mint,           isSigner: false, isWritable: false }, // mint
                { pubkey: authority,      isSigner: true,  isWritable: false }, // mint authority
                { pubkey: authority,      isSigner: true,  isWritable: true  }, // payer
                { pubkey: authority,      isSigner: false, isWritable: false }, // update authority
                { pubkey: SYSTEM_PROGRAM, isSigner: false, isWritable: false }, // system program
                { pubkey: RENT_SYSVAR,    isSigner: false, isWritable: false }, // rent (optional but safe)
            ],
            data: d,
        });
    }

    // ═══════════════════════════════════════════════════════════
    //  BYTE HELPERS
    // ═══════════════════════════════════════════════════════════

    function writeU32(buf, val, off) {
        buf[off]     =  val        & 0xFF;
        buf[off + 1] = (val >> 8)  & 0xFF;
        buf[off + 2] = (val >> 16) & 0xFF;
        buf[off + 3] = (val >> 24) & 0xFF;
    }

    function writeU64(buf, val, off) {
        let v = BigInt(val);
        for (let i = 0; i < 8; i++) {
            buf[off + i] = Number(v & 0xFFn);
            v >>= 8n;
        }
    }

    /** Base58 encode a Uint8Array (for WalletStandard signature conversion) */
    function b58(bytes) {
        const A = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
        let r = '', n = 0n;
        for (const b of bytes) n = n * 256n + BigInt(b);
        while (n > 0n) { r = A[Number(n % 58n)] + r; n /= 58n; }
        for (const b of bytes) { if (b === 0) r = '1' + r; else break; }
        return r || '1';
    }

    // ═══════════════════════════════════════════════════════════
    //  UI HELPERS
    // ═══════════════════════════════════════════════════════════

    function showStatus(state, title, body) {
        const panel = $('#status-panel');
        if (!panel) return;
        panel.className = 'tf-status active ' + state;

        const sp = $('#status-spinner');
        if (sp) sp.style.display = (state === 'success' || state === 'error') ? 'none' : 'inline-block';

        const t = $('#status-title');
        if (t) t.textContent = title;

        const b = $('#status-body');
        if (b) b.innerHTML = body;

        panel.scrollIntoView({ behavior: 'smooth', block: 'nearest' });
    }

    function disableForm(yes) {
        document.querySelectorAll('.tf-input,.tf-textarea,.tf-select,#revoke-mint,#revoke-freeze').forEach(el => el.disabled = yes);
        const btn = $('#deploy-btn');
        if (btn) btn.disabled = yes;
    }

    /** Result row for status body */
    function rRow(label, value) {
        return '<div class="tf-result-row"><span class="tf-result-label">' + label + '</span><span class="tf-result-value">' + value + '</span></div>';
    }

    /** Modal confirm row */
    function mRow(label, value) {
        return '<div class="tf-modal-row"><span class="tf-modal-label">' + label + '</span><span class="tf-modal-value">' + value + '</span></div>';
    }

    function truncAddr(a) {
        if (!a || a.length < 12) return a || '';
        return a.slice(0, 4) + '...' + a.slice(-4);
    }

    function esc(s) {
        const d = document.createElement('div');
        d.textContent = s;
        return d.innerHTML;
    }
})();
