import { PublicKey, Transaction, VersionedTransaction } from './js/lib/vendor/solana-publickey-bundle.js';

const PAGE_REQUEST = 'shine-wallet-page-request';
const PAGE_RESPONSE = 'shine-wallet-page-response';
const PAGE_MESSAGE_TARGET_ORIGIN = '*';
const STANDARD_REGISTER_EVENT = 'wallet-standard:register-wallet';
const STANDARD_APP_READY_EVENT = 'wallet-standard:app-ready';
const SOLANA_CHAINS = ['solana:mainnet', 'solana:devnet', 'solana:testnet'];
const SOLANA_STANDARD_FEATURES = ['solana:signTransaction'];
const WALLET_ICON = `data:image/svg+xml;base64,${btoa(
  '<svg xmlns="http://www.w3.org/2000/svg" width="96" height="96" viewBox="0 0 96 96" fill="none"><rect width="96" height="96" rx="20" fill="#101722"/><path d="M23 28h50l-8 10H31l-8-10Z" fill="#3FB0FF"/><path d="M31 44h34l8 10H23l8-10Z" fill="#77D67A"/><path d="M23 68h50l-8-10H31l-8 10Z" fill="#A881FF"/></svg>'
)}`;

function bytesToBase64(bytes) {
  let binary = '';
  const chunk = 0x8000;
  for (let i = 0; i < bytes.length; i += chunk) {
    const slice = bytes.subarray(i, i + chunk);
    binary += String.fromCharCode(...slice);
  }
  return btoa(binary);
}

function base64ToBytes(value) {
  const binary = atob(String(value || '').trim());
  const out = new Uint8Array(binary.length);
  for (let i = 0; i < binary.length; i += 1) {
    out[i] = binary.charCodeAt(i);
  }
  return out;
}

function createProviderError(message, code = '') {
  const error = new Error(String(message || 'Wallet provider error'));
  if (code === 'USER_REJECTED' || code === 'NOT_TRUSTED') {
    error.code = 4001;
  } else if (code) {
    error.code = code;
  }
  return error;
}

function summarizeTransaction(transaction) {
  const summary = {
    kind: 'legacy',
    instructionCount: 0,
    accountCount: 0,
    feePayer: '',
    recentBlockhash: '',
    programs: [],
  };
  if (!transaction) return summary;

  const isVersioned = typeof transaction?.version === 'number' || transaction instanceof VersionedTransaction;
  summary.kind = isVersioned ? `versioned:${String(transaction.version)}` : 'legacy';
  summary.feePayer = String(transaction?.feePayer?.toBase58?.() || '').trim();
  summary.recentBlockhash = String(transaction?.recentBlockhash || transaction?.message?.recentBlockhash || '').trim();

  if (isVersioned) {
    const message = transaction?.message || {};
    const staticKeys = Array.isArray(message?.staticAccountKeys) ? message.staticAccountKeys : [];
    const instructions = Array.isArray(message?.compiledInstructions) ? message.compiledInstructions : [];
    summary.instructionCount = instructions.length;
    summary.accountCount = staticKeys.length;
    summary.programs = instructions
      .map((instruction) => staticKeys[instruction?.programIdIndex]?.toBase58?.() || '')
      .filter(Boolean)
      .slice(0, 5);
    return summary;
  }

  const instructions = Array.isArray(transaction?.instructions) ? transaction.instructions : [];
  summary.instructionCount = instructions.length;
  summary.accountCount = Array.isArray(transaction?.signatures) ? transaction.signatures.length : 0;
  summary.programs = instructions
    .map((instruction) => instruction?.programId?.toBase58?.() || '')
    .filter(Boolean)
    .slice(0, 5);
  return summary;
}

function createRequest(method, params = {}) {
  const id = `shine-wallet-${Date.now()}-${Math.random().toString(16).slice(2, 8)}`;
  return new Promise((resolve, reject) => {
    const onMessage = (event) => {
      if (event.source !== window) return;
      const data = event.data || {};
      if (data?.target !== PAGE_RESPONSE || String(data?.id || '') !== id) return;
      window.removeEventListener('message', onMessage);
      if (!data?.ok) {
        reject(createProviderError(data?.error || 'Wallet request failed', String(data?.code || '')));
        return;
      }
      resolve(data?.result || {});
    };
    window.addEventListener('message', onMessage);
    window.postMessage({
      target: PAGE_REQUEST,
      id,
      method,
      params,
    }, PAGE_MESSAGE_TARGET_ORIGIN);
  });
}

function serializeTransactionBase64(transaction) {
  if (!transaction || typeof transaction.serialize !== 'function') {
    throw createProviderError('Unsupported transaction object', 'UNSUPPORTED_TRANSACTION');
  }
  let raw;
  try {
    raw = transaction.serialize({ requireAllSignatures: false, verifySignatures: false });
  } catch {
    raw = transaction.serialize();
  }
  const bytes = raw instanceof Uint8Array ? raw : new Uint8Array(raw);
  return bytesToBase64(bytes);
}

function deserializeSignedTransaction(base64, originalTransaction) {
  const bytes = base64ToBytes(base64);
  const ctor = originalTransaction?.constructor;
  if (ctor && typeof ctor.deserialize === 'function') {
    return ctor.deserialize(bytes);
  }
  if (ctor && typeof ctor.from === 'function') {
    return ctor.from(bytes);
  }
  if (typeof originalTransaction?.version === 'number') {
    return VersionedTransaction.deserialize(bytes);
  }
  return Transaction.from(bytes);
}

class ShineWalletAccount {
  constructor(publicKeyBase58) {
    this.address = publicKeyBase58;
    this.publicKey = new Uint8Array(new PublicKey(publicKeyBase58).toBytes());
    this.chains = SOLANA_CHAINS.slice();
    this.features = SOLANA_STANDARD_FEATURES.slice();
    this.label = 'SHiNE Wallet';
    this.icon = WALLET_ICON;
  }
}

class ShineProviderCore {
  constructor() {
    this.publicKey = null;
    this.isConnected = false;
    this._legacyListeners = new Map();
    this._standardListeners = new Set();
    this._accounts = [];
  }

  get publicKeyBase58() {
    return this.publicKey?.toBase58?.() || '';
  }

  get standardAccounts() {
    return this._accounts.slice();
  }

  async connect(options = {}) {
    const onlyIfTrusted = !!options?.onlyIfTrusted || !!options?.silent;
    const result = await createRequest('connect', { onlyIfTrusted });
    const nextKey = new PublicKey(String(result?.publicKeyBase58 || '').trim());
    this.publicKey = nextKey;
    this.isConnected = true;
    this._accounts = [new ShineWalletAccount(nextKey.toBase58())];
    this.emitLegacy('connect', nextKey);
    this.emitLegacy('accountChanged', nextKey);
    this.emitStandardChange();
    return {
      publicKey: nextKey,
      accounts: this.standardAccounts,
    };
  }

  async disconnect() {
    await createRequest('disconnect', {});
    this.isConnected = false;
    this.publicKey = null;
    this._accounts = [];
    this.emitLegacy('disconnect');
    this.emitLegacy('accountChanged', null);
    this.emitStandardChange();
  }

  async signTransaction(transaction, comment = '') {
    if (!this.publicKey) {
      await this.connect();
    }
    const transactionBase64 = serializeTransactionBase64(transaction);
    const transactionSummary = summarizeTransaction(transaction);
    const result = await createRequest('signTransaction', {
      publicKeyBase58: this.publicKeyBase58,
      transactionBase64,
      comment: String(comment || '').trim() || `Site ${window.location.origin} requested transaction signature`,
      transactionSummary,
    });
    return deserializeSignedTransaction(String(result?.signedTransactionBase64 || ''), transaction);
  }

  async signTransactionBytes(transactionBytes, comment = '') {
    if (!this.publicKey) {
      await this.connect();
    }
    const transactionSummary = {
      kind: 'raw-bytes',
      instructionCount: 0,
      accountCount: 0,
      feePayer: this.publicKeyBase58,
      recentBlockhash: '',
      programs: [],
      byteLength: Number(transactionBytes?.length || 0),
    };
    const result = await createRequest('signTransaction', {
      publicKeyBase58: this.publicKeyBase58,
      transactionBase64: bytesToBase64(transactionBytes),
      comment: String(comment || '').trim() || `Site ${window.location.origin} requested transaction signature`,
      transactionSummary,
    });
    return base64ToBytes(String(result?.signedTransactionBase64 || '').trim());
  }

  onLegacy(event, handler) {
    const key = String(event || '');
    if (!this._legacyListeners.has(key)) {
      this._legacyListeners.set(key, new Set());
    }
    this._legacyListeners.get(key).add(handler);
    return this;
  }

  offLegacy(event, handler) {
    const key = String(event || '');
    const bucket = this._legacyListeners.get(key);
    if (!bucket) return this;
    bucket.delete(handler);
    if (!bucket.size) this._legacyListeners.delete(key);
    return this;
  }

  emitLegacy(event, payload) {
    const bucket = this._legacyListeners.get(String(event || ''));
    if (!bucket?.size) return;
    for (const handler of [...bucket]) {
      try {
        handler(payload);
      } catch {}
    }
  }

  onStandardChange(listener) {
    this._standardListeners.add(listener);
    return () => {
      this._standardListeners.delete(listener);
    };
  }

  emitStandardChange() {
    const properties = { accounts: this.standardAccounts };
    for (const listener of [...this._standardListeners]) {
      try {
        listener(properties);
      } catch {}
    }
  }
}

class ShineSolanaProvider {
  constructor(core) {
    this.core = core;
    this.isSHiNE = true;
    this.isPhantom = true;
  }

  get publicKey() {
    return this.core.publicKey;
  }

  get isConnected() {
    return this.core.isConnected;
  }

  on(event, handler) {
    return this.core.onLegacy(event, handler);
  }

  off(event, handler) {
    return this.core.offLegacy(event, handler);
  }

  removeListener(event, handler) {
    return this.off(event, handler);
  }

  async connect(options = {}) {
    const result = await this.core.connect(options);
    return { publicKey: result.publicKey };
  }

  async disconnect() {
    await this.core.disconnect();
  }

  async signTransaction(transaction) {
    return this.core.signTransaction(transaction);
  }

  async signAllTransactions(transactions = []) {
    const list = Array.isArray(transactions) ? transactions : [];
    const outputs = [];
    for (const transaction of list) {
      outputs.push(await this.core.signTransaction(transaction));
    }
    return outputs;
  }

  async request(args = {}) {
    const method = String(args?.method || '');
    const params = args?.params;
    if (method === 'connect') {
      return this.connect(Array.isArray(params) ? params[0] : params || {});
    }
    if (method === 'disconnect') {
      return this.disconnect();
    }
    if (method === 'signTransaction') {
      const tx = Array.isArray(params) ? params[0] : params?.transaction || params;
      return this.signTransaction(tx);
    }
    if (method === 'signAllTransactions') {
      const transactions = Array.isArray(params)
        ? params
        : Array.isArray(params?.transactions) ? params.transactions : [];
      return this.signAllTransactions(transactions);
    }
    throw createProviderError(`Unsupported request method: ${method}`, 'UNSUPPORTED_METHOD');
  }
}

class ShineStandardWallet {
  constructor(core) {
    this.core = core;
    this.version = '1.0.0';
    this.name = 'SHiNE Wallet';
    this.icon = WALLET_ICON;
    this.chains = SOLANA_CHAINS.slice();
    this.features = {
      'standard:connect': {
        version: '1.0.0',
        connect: async (input = {}) => {
          const result = await this.core.connect({ silent: !!input?.silent });
          return { accounts: result.accounts };
        },
      },
      'standard:disconnect': {
        version: '1.0.0',
        disconnect: async () => {
          await this.core.disconnect();
        },
      },
      'standard:events': {
        version: '1.0.0',
        on: (event, listener) => {
          if (event !== 'change' || typeof listener !== 'function') {
            return () => {};
          }
          return this.core.onStandardChange(listener);
        },
      },
      'solana:signTransaction': {
        version: '1.0.0',
        supportedTransactionVersions: ['legacy', 0],
        signTransaction: async (...inputs) => {
          const outputs = [];
          for (const input of inputs) {
            const accountAddress = String(input?.account?.address || '').trim();
            if (accountAddress && this.core.publicKeyBase58 && accountAddress !== this.core.publicKeyBase58) {
              throw createProviderError('Requested account does not match current wallet account', 'ACCOUNT_MISMATCH');
            }
            const comment = `Site ${window.location.origin} requested transaction signature`;
            const signedTransaction = await this.core.signTransactionBytes(new Uint8Array(input.transaction), comment);
            outputs.push({ signedTransaction });
          }
          return outputs;
        },
      },
    };
  }

  get accounts() {
    return this.core.standardAccounts;
  }
}

function registerStandardWallet(wallet) {
  const callback = ({ register }) => register(wallet);
  try {
    window.dispatchEvent(new CustomEvent(STANDARD_REGISTER_EVENT, { detail: callback }));
  } catch (error) {
    console.error('wallet-standard register dispatch failed', error);
  }
  try {
    window.addEventListener(STANDARD_APP_READY_EVENT, ({ detail }) => {
      try {
        callback(detail);
      } catch (error) {
        console.error('wallet-standard app-ready callback failed', error);
      }
    });
  } catch (error) {
    console.error('wallet-standard app-ready listener failed', error);
  }
  try {
    window.navigator.wallets = window.navigator.wallets || [];
    window.navigator.wallets.push(callback);
  } catch {}
}

const core = new ShineProviderCore();
const legacyProvider = new ShineSolanaProvider(core);
const standardWallet = new ShineStandardWallet(core);

registerStandardWallet(standardWallet);

if (!window.solana) {
  window.solana = legacyProvider;
  window.phantom = window.phantom || {};
  window.phantom.solana = legacyProvider;
  window.dispatchEvent(new Event('solana#initialized'));
}
