/**
 * FurryPlace SDK - Button Extension, User State, Info Modal & Shop API
 *
 * This SDK allows external scripts to:
 * - Add custom buttons to the FurryPlace UI
 * - Access user state (login status, charges, droplets, level, etc.)
 * - Add custom sections to the info modal
 * - Add custom items to the shop
 * - Purchase store items programmatically
 * - React to user state changes
 * - Access site content and social links
 *
 * @example Basic Button
 * window.FurryPlaceSDK.registerButton({
 *   id: 'my-custom-button',
 *   title: 'My Button',
 *   icon: '<svg>...</svg>',
 *   position: 'before-leaderboard',
 *   onClick: (context) => {
 *     console.log('Button clicked!', context);
 *   },
 *   condition: (context) => context.user?.isLoggedIn
 * });
 *
 * @example Accessing User State
 * // Check if logged in
 * if (window.FurryPlaceSDK.isLoggedIn()) {
 *   console.log('User is logged in!');
 *   console.log('Droplets:', window.FurryPlaceSDK.getDroplets());
 *   console.log('Charges:', window.FurryPlaceSDK.getCharges());
 *   console.log('Level:', window.FurryPlaceSDK.getLevel());
 * }
 *
 * @example Async User Data
 * const user = await window.FurryPlaceSDK.getUser();
 * if (user) {
 *   console.log('User name:', user.name);
 *   console.log('Pixels painted:', user.pixelsPainted);
 * }
 *
 * @example Info Modal Customization
 * window.FurryPlaceSDK.addInfoSection({
 *   id: 'my-section',
 *   title: 'Custom Section',
 *   content: '<p>Custom HTML content here</p>',
 *   position: 'after-video'
 * });
 *
 * @example Adding Custom Shop Items
 * window.FurryPlaceSDK.addShopItem({
 *   id: 'custom-powerup',
 *   name: 'Super Paint Boost',
 *   description: 'Double your paint speed for 1 hour',
 *   price: 1000,
 *   position: 'top',
 *   onPurchase: async (context) => {
 *     // Check if user has enough droplets
 *     if (context.user.droplets < 1000) {
 *       alert('Not enough droplets!');
 *       return;
 *     }
 *     // Your custom purchase logic here
 *     const success = await yourCustomPurchaseAPI();
 *     if (success) {
 *       alert('Purchase successful!');
 *     }
 *   },
 *   condition: (context) => context.user?.isLoggedIn
 * });
 *
 * @example Purchasing Store Items
 * // Purchase +5 max charges
 * const success = await window.FurryPlaceSDK.purchaseStoreItem(70, 1);
 *
 * // Purchase +30 paint charges
 * await window.FurryPlaceSDK.purchaseStoreItem(80, 2); // Buy 2x
 *
 * // Unlock a specific paid color (variant 32-63)
 * await window.FurryPlaceSDK.purchaseStoreItem(100, 1, 35);
 *
 * // Unlock a flag (variant 1-251)
 * await window.FurryPlaceSDK.purchaseStoreItem(110, 1, 42);
 *
 * @example Accessing Site Content and Social Links
 * // Get all site content
 * const content = await window.FurryPlaceSDK.getSiteContent('en');
 * console.log('Site title:', content['site.title']);
 *
 * // Get social links
 * const socials = await window.FurryPlaceSDK.getSocialLinks('en');
 * console.log('Twitter URL:', socials.twitter.url);
 * console.log('Bluesky URL:', socials.bluesky.url);
 * console.log('Discord URL:', socials.discord.url);
 */

(function() {
  'use strict';

  const SDK_VERSION = '2.2.0';
  const registeredButtons = [];
  const infoModalSections = [];
  const shopItems = [];
  const captchaCallbacks = {};
  let isInitialized = false;
  let injectionPoint = null;
  let infoModalObserver = null;
  let shopModalObserver = null;

  // Find the button container in the DOM
  function findButtonContainer() {
    // Look for the container with class "flex flex-col items-center gap-3"
    const containers = document.querySelectorAll('.flex.flex-col.items-center.gap-3');
    for (const container of containers) {
      // Verify it contains the expected buttons by checking for specific classes
      const hasExpectedButtons = container.querySelector('.btn.btn-square.shadow-md');
      if (hasExpectedButtons) {
        return container;
      }
    }
    return null;
  }

  // Wait for the DOM to contain the button container
  function waitForButtonContainer(callback, maxAttempts = 50) {
    let attempts = 0;
    const interval = setInterval(() => {
      const container = findButtonContainer();
      if (container) {
        clearInterval(interval);
        callback(container);
      } else if (++attempts >= maxAttempts) {
        clearInterval(interval);
        console.error('[FurryPlace SDK] Could not find button container after', maxAttempts, 'attempts');
      }
    }, 100);
  }

  // Create a button element
  function createButton(config) {
    const wrapper = document.createElement('div');
    wrapper.setAttribute('data-furryplace-button', config.id);
    wrapper.className = config.wrapperClass || '';

    const button = document.createElement('button');
    button.className = config.className || 'btn btn-square shadow-md';
    button.title = config.title || '';

    if (config.disabled) {
      button.disabled = true;
    }

    // Add icon
    if (config.icon) {
      if (typeof config.icon === 'string') {
        button.innerHTML = config.icon;
      } else if (config.icon instanceof HTMLElement) {
        button.appendChild(config.icon);
      }
    }

    // Add click handler
    if (config.onClick) {
      button.addEventListener('click', (e) => {
        e.preventDefault();
        e.stopPropagation();

        const context = getContext();
        config.onClick(context, e);
      });
    }

    wrapper.appendChild(button);
    return wrapper;
  }

  // User state cache
  let userState = null;
  let userStatePromise = null;

  // Site content cache
  let siteContent = null;
  let siteContentPromise = null;

  // Fetch and cache user state
  async function fetchUserState() {
    try {
      const response = await fetch('/api/me', {
        credentials: 'include'
      });
      if (response.ok) {
        userState = await response.json();
        return userState;
      }
      return null;
    } catch (error) {
      console.warn('[FurryPlace SDK] Failed to fetch user state:', error);
      return null;
    }
  }

  // Get user state (cached)
  async function getUserState() {
    if (userState) return userState;
    if (!userStatePromise) {
      userStatePromise = fetchUserState();
    }
    userState = await userStatePromise;
    userStatePromise = null;
    return userState;
  }

  // Refresh user state
  function refreshUserState() {
    userState = null;
    userStatePromise = null;
    return getUserState();
  }

  // Fetch and cache site content
  async function fetchSiteContent(locale = 'en') {
    try {
      const response = await fetch(`/api/site-content?locale=${locale}`);
      if (response.ok) {
        const data = await response.json();
        siteContent = data.content;
        return siteContent;
      }
      return null;
    } catch (error) {
      console.warn('[FurryPlace SDK] Failed to fetch site content:', error);
      return null;
    }
  }

  // Get site content (cached)
  async function getSiteContent(locale = 'en') {
    if (siteContent) return siteContent;
    if (!siteContentPromise) {
      siteContentPromise = fetchSiteContent(locale);
    }
    siteContent = await siteContentPromise;
    siteContentPromise = null;
    return siteContent;
  }

  // Refresh site content
  function refreshSiteContent(locale = 'en') {
    siteContent = null;
    siteContentPromise = null;
    return getSiteContent(locale);
  }

  // Find info modal content area
  function findInfoModal() {
    // Look for modal with specific structure (has iframe for YouTube)
    const modals = document.querySelectorAll('dialog.modal');
    for (const modal of modals) {
      const iframe = modal.querySelector('iframe[title*="YouTube"]');
      if (iframe) {
        return modal.querySelector('.modal-box');
      }
    }
    return null;
  }

  // Create a custom section element
  function createInfoSection(config) {
    const section = document.createElement('section');
    section.setAttribute('data-furryplace-info', config.id);

    if (config.className) {
      section.className = config.className;
    }

    // Add title if provided
    if (config.title) {
      const title = document.createElement('h3');
      title.className = 'text-lg font-semibold mb-2';
      title.textContent = config.title;
      section.appendChild(title);
    }

    // Add content
    if (typeof config.content === 'string') {
      const contentDiv = document.createElement('div');
      contentDiv.innerHTML = config.content;
      section.appendChild(contentDiv);
    } else if (config.content instanceof HTMLElement) {
      section.appendChild(config.content);
    } else if (typeof config.content === 'function') {
      const contentResult = config.content();
      if (contentResult instanceof HTMLElement) {
        section.appendChild(contentResult);
      } else if (typeof contentResult === 'string') {
        const contentDiv = document.createElement('div');
        contentDiv.innerHTML = contentResult;
        section.appendChild(contentDiv);
      }
    }

    return section;
  }

  // Inject custom sections into info modal
  function injectInfoSections() {
    const modalBox = findInfoModal();
    if (!modalBox) return;

    // Remove old custom sections
    const oldSections = modalBox.querySelectorAll('[data-furryplace-info]');
    oldSections.forEach(section => section.remove());

    // Find the content container (first div inside modal-box)
    let contentContainer = modalBox.querySelector('div[class*="flex"]');

    // If no container found, create one
    if (!contentContainer) {
      contentContainer = document.createElement('div');
      contentContainer.className = 'flex h-full flex-col gap-5';
      modalBox.appendChild(contentContainer);
    }

    // Find insertion point (before the last section which contains links/footer)
    const sections = contentContainer.querySelectorAll('section');
    const lastSection = sections[sections.length - 1];

    // Inject each custom section
    infoModalSections.forEach(config => {
      const section = createInfoSection(config);

      if (config.position === 'top') {
        contentContainer.insertBefore(section, contentContainer.firstChild);
      } else if (config.position === 'bottom' || !config.position) {
        if (lastSection && lastSection.classList.contains('text-center')) {
          // Insert before footer section
          contentContainer.insertBefore(section, lastSection);
        } else {
          contentContainer.appendChild(section);
        }
      } else if (config.position === 'after-video') {
        // Find YouTube iframe section
        const videoSection = Array.from(sections).find(s => s.querySelector('iframe[title*="YouTube"]'));
        if (videoSection && videoSection.nextSibling) {
          contentContainer.insertBefore(section, videoSection.nextSibling);
        } else {
          contentContainer.appendChild(section);
        }
      }
    });
  }

  // Watch for info modal opening
  function watchInfoModal() {
    if (infoModalObserver) return;

    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        // Watch for new dialogs being added
        mutation.addedNodes.forEach((node) => {
          if (node.nodeName === 'DIALOG' && node.classList.contains('modal')) {
            // Check if this is the info modal
            setTimeout(() => {
              const modalBox = findInfoModal();
              if (modalBox && node.open) {
                injectInfoSections();
              }
            }, 50);
          }
        });

        // Also watch for 'open' attribute changes on existing dialogs
        if (mutation.type === 'attributes' && mutation.attributeName === 'open') {
          const target = mutation.target;
          if (target.nodeName === 'DIALOG' && target.open) {
            setTimeout(() => {
              const modalBox = findInfoModal();
              if (modalBox) {
                injectInfoSections();
              }
            }, 50);
          }
        }
      });
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ['open']
    });

    infoModalObserver = observer;
  }

  // Find shop modal content area
  function findShopModal() {
    // Look for modal with shop content (contains "Shop" text or store items)
    const modals = document.querySelectorAll('dialog.modal');
    for (const modal of modals) {
      const modalBox = modal.querySelector('.modal-box');
      if (modalBox && modal.open) {
        // Check if this is the shop modal by looking for typical shop elements
        const hasShopContent = modalBox.querySelector('[data-shop-content]') ||
                               modalBox.querySelector('.shop-items') ||
                               (modalBox.textContent && modalBox.textContent.includes('Droplets'));
        if (hasShopContent) {
          return modalBox;
        }
      }
    }
    return null;
  }

  // Create a custom shop item element
  function createShopItem(config) {
    const item = document.createElement('div');
    item.setAttribute('data-furryplace-shop-item', config.id);
    item.className = config.className || 'flex items-center justify-between p-4 border rounded-lg hover:bg-base-200 transition-colors';

    // Item info container
    const info = document.createElement('div');
    info.className = 'flex-1';

    // Item title
    const title = document.createElement('div');
    title.className = 'font-semibold text-lg';
    title.textContent = config.name;
    info.appendChild(title);

    // Item description
    if (config.description) {
      const desc = document.createElement('div');
      desc.className = 'text-sm text-base-content/70';
      desc.textContent = config.description;
      info.appendChild(desc);
    }

    item.appendChild(info);

    // Price and purchase button container
    const actionContainer = document.createElement('div');
    actionContainer.className = 'flex items-center gap-3';

    // Price display
    const priceDiv = document.createElement('div');
    priceDiv.className = 'text-lg font-bold';
    priceDiv.textContent = config.price + ' 💧';
    actionContainer.appendChild(priceDiv);

    // Purchase button
    const button = document.createElement('button');
    button.className = config.buttonClassName || 'btn btn-primary';
    button.textContent = config.buttonText || 'Purchase';

    if (config.disabled) {
      button.disabled = true;
    }

    // Add click handler
    if (config.onPurchase) {
      button.addEventListener('click', async (e) => {
        e.preventDefault();
        e.stopPropagation();

        const context = getContext();

        // Disable button during purchase
        button.disabled = true;
        const originalText = button.textContent;
        button.textContent = 'Purchasing...';

        try {
          await config.onPurchase(context, e);
          // Refresh user state after purchase
          await refreshUserState();
        } catch (error) {
          console.error('[FurryPlace SDK] Purchase failed:', error);
        } finally {
          button.disabled = config.disabled || false;
          button.textContent = originalText;
        }
      });
    }

    actionContainer.appendChild(button);
    item.appendChild(actionContainer);

    return item;
  }

  // Inject custom shop items
  function injectShopItems() {
    const modalBox = findShopModal();
    if (!modalBox) return;

    // Remove old custom items
    const oldItems = modalBox.querySelectorAll('[data-furryplace-shop-item]');
    oldItems.forEach(item => item.remove());

    // Find or create shop items container
    let itemsContainer = modalBox.querySelector('[data-shop-content]');
    if (!itemsContainer) {
      // Try to find a suitable container
      itemsContainer = modalBox.querySelector('.flex.flex-col.gap-4');
      if (!itemsContainer) {
        // Create one if needed
        itemsContainer = document.createElement('div');
        itemsContainer.className = 'flex flex-col gap-4';
        itemsContainer.setAttribute('data-shop-content', 'true');
        modalBox.appendChild(itemsContainer);
      }
    }

    // Inject each custom shop item
    shopItems.forEach(config => {
      // Check condition
      if (config.condition) {
        const context = getContext();
        if (!config.condition(context)) {
          return; // Skip this item
        }
      }

      const item = createShopItem(config);

      // Determine insertion position
      if (config.position === 'top') {
        itemsContainer.insertBefore(item, itemsContainer.firstChild);
      } else if (config.position === 'bottom' || !config.position) {
        itemsContainer.appendChild(item);
      }
    });
  }

  // Watch for shop modal opening
  function watchShopModal() {
    if (shopModalObserver) return;

    const observer = new MutationObserver((mutations) => {
      mutations.forEach((mutation) => {
        // Watch for new dialogs being added
        mutation.addedNodes.forEach((node) => {
          if (node.nodeName === 'DIALOG' && node.classList.contains('modal')) {
            setTimeout(() => {
              const modalBox = findShopModal();
              if (modalBox && node.open) {
                injectShopItems();
              }
            }, 50);
          }
        });

        // Also watch for 'open' attribute changes on existing dialogs
        if (mutation.type === 'attributes' && mutation.attributeName === 'open') {
          const target = mutation.target;
          if (target.nodeName === 'DIALOG' && target.open) {
            setTimeout(() => {
              const modalBox = findShopModal();
              if (modalBox) {
                injectShopItems();
              }
            }, 50);
          }
        }
      });
    });

    observer.observe(document.body, {
      childList: true,
      subtree: true,
      attributes: true,
      attributeFilter: ['open']
    });

    shopModalObserver = observer;
  }

  // Get context information for button callbacks
  function getContext() {
    const isLoggedIn = !!userState;

    return {
      sdk: {
        version: SDK_VERSION,
        refreshUserState: refreshUserState,
      },
      user: {
        isLoggedIn: isLoggedIn,
        id: userState?.id || null,
        name: userState?.name || null,
        discord: userState?.discord || null,
        country: userState?.country || null,
        banned: userState?.banned || false,
        role: userState?.role || null,
        level: userState?.level || 0,
        pixelsPainted: userState?.pixelsPainted || 0,
        droplets: userState?.droplets || 0,
        picture: userState?.picture || null,
        equippedFlag: userState?.equippedFlag || null,
        allianceId: userState?.allianceId || null,
        allianceRole: userState?.allianceRole || null,
        charges: {
          current: userState?.charges?.count || 0,
          max: userState?.charges?.max || 0,
          cooldownMs: userState?.charges?.cooldownMs || 0,
          percentage: userState?.charges?.max > 0
            ? (userState.charges.count / userState.charges.max) * 100
            : 0
        },
        hasChargesToPaint: (userState?.charges?.count || 0) >= 1,
        needsPhoneVerification: userState?.needsPhoneVerification || false,
        isTimedOut: !!userState?.timeoutUntil,
        timeoutUntil: userState?.timeoutUntil || null,
      },
      map: {
        // Map context if available
      }
    };
  }

  // Inject buttons into the DOM
  function injectButtons(container) {
    if (!container) return;

    registeredButtons.forEach(config => {
      // Check condition
      if (config.condition) {
        const context = getContext();
        if (!config.condition(context)) {
          return; // Skip this button
        }
      }

      const button = createButton(config);

      // Determine insertion position
      switch (config.position) {
        case 'top':
          container.insertBefore(button, container.firstChild);
          break;
        case 'bottom':
          container.appendChild(button);
          break;
        case 'before-leaderboard': {
          // Find leaderboard button (has specific SVG viewBox)
          const leaderboardBtn = Array.from(container.querySelectorAll('button')).find(btn => {
            const svg = btn.querySelector('svg[viewBox="0 -960 960 960"]');
            return svg && svg.querySelector('path[d*="160-200h160v-320"]');
          });
          if (leaderboardBtn && leaderboardBtn.parentElement) {
            container.insertBefore(button, leaderboardBtn.parentElement);
          } else {
            container.appendChild(button);
          }
          break;
        }
        case 'after-leaderboard': {
          const leaderboardBtn = Array.from(container.querySelectorAll('button')).find(btn => {
            const svg = btn.querySelector('svg[viewBox="0 -960 960 960"]');
            return svg && svg.querySelector('path[d*="160-200h160v-320"]');
          });
          if (leaderboardBtn && leaderboardBtn.parentElement && leaderboardBtn.parentElement.nextSibling) {
            container.insertBefore(button, leaderboardBtn.parentElement.nextSibling);
          } else {
            container.appendChild(button);
          }
          break;
        }
        default:
          container.appendChild(button);
      }
    });

    injectionPoint = container;
  }

  // Re-inject buttons (useful when the app re-renders)
  function reinjectButtons() {
    if (!injectionPoint) return;

    // Remove old injected buttons
    const oldButtons = injectionPoint.querySelectorAll('[data-furryplace-button]');
    oldButtons.forEach(btn => btn.remove());

    // Re-inject
    injectButtons(injectionPoint);
  }

  // Initialize the SDK
  async function initialize() {
    if (isInitialized) {
      console.warn('[FurryPlace SDK] Already initialized');
      return;
    }

    console.log('[FurryPlace SDK] Initializing v' + SDK_VERSION);

    // Fetch user state first
    await getUserState();

    waitForButtonContainer((container) => {
      console.log('[FurryPlace SDK] Found button container, injecting buttons...');
      injectButtons(container);
      isInitialized = true;

      // DISABLED: The mutation observer was causing infinite loops
      // Buttons will be injected once and stay there
      // If the app re-renders the container, buttons may disappear
      // but this is safer than causing freezes

      /*
      // Set up mutation observer to re-inject on DOM changes
      const observer = new MutationObserver(() => {
        const currentContainer = findButtonContainer();
        if (currentContainer && currentContainer !== injectionPoint) {
          injectButtons(currentContainer);
        } else if (currentContainer && injectionPoint) {
          const ourButtons = currentContainer.querySelectorAll('[data-furryplace-button]');
          if (ourButtons.length !== registeredButtons.length) {
            reinjectButtons();
          }
        }
      });

      observer.observe(document.body, {
        childList: true,
        subtree: true,
      });
      */
    });

    // Refresh user state periodically (every 5 seconds)
    setInterval(() => {
      refreshUserState();
    }, 5000);

    // Start watching for info modal
    watchInfoModal();

    // Start watching for shop modal
    watchShopModal();
  }

  // Public API
  window.FurryPlaceSDK = {
    version: SDK_VERSION,

    /**
     * Register a custom button
     * @param {Object} config - Button configuration
     * @param {string} config.id - Unique identifier for the button
     * @param {string} config.title - Tooltip text
     * @param {string|HTMLElement} config.icon - SVG string or element
     * @param {Function} config.onClick - Click handler (context, event) => {}
     * @param {string} [config.position='bottom'] - Where to place the button
     * @param {string} [config.className] - Custom CSS classes
     * @param {string} [config.wrapperClass] - Wrapper div CSS classes
     * @param {Function} [config.condition] - Optional condition (context) => boolean
     * @param {boolean} [config.disabled=false] - Whether button is disabled
     */
    registerButton(config) {
      if (!config.id) {
        console.error('[FurryPlace SDK] Button must have an id');
        return;
      }

      if (registeredButtons.find(b => b.id === config.id)) {
        console.error('[FurryPlace SDK] Button with id "' + config.id + '" already registered');
        return;
      }

      registeredButtons.push(config);
      console.log('[FurryPlace SDK] Registered button:', config.id);

      // If already initialized, inject immediately
      if (isInitialized && injectionPoint) {
        const button = createButton(config);
        injectionPoint.appendChild(button);
      }
    },

    /**
     * Unregister a button
     * @param {string} id - Button ID to remove
     */
    unregisterButton(id) {
      const index = registeredButtons.findIndex(b => b.id === id);
      if (index !== -1) {
        registeredButtons.splice(index, 1);

        // Remove from DOM
        if (injectionPoint) {
          const element = injectionPoint.querySelector('[data-furryplace-button="' + id + '"]');
          if (element) {
            element.remove();
          }
        }

        console.log('[FurryPlace SDK] Unregistered button:', id);
      }
    },

    /**
     * Get list of registered buttons
     */
    getButtons() {
      return registeredButtons.map(b => ({ id: b.id, title: b.title }));
    },

    /**
     * Manually trigger SDK initialization
     */
    init() {
      initialize();
    },

    /**
     * Get current context
     */
    getContext() {
      return getContext();
    },

    /**
     * Get user state (async)
     * @returns {Promise<Object>} User data or null if not logged in
     */
    async getUser() {
      return await getUserState();
    },

    /**
     * Refresh user state from server
     * @returns {Promise<Object>} Updated user data or null
     */
    async refreshUser() {
      return await refreshUserState();
    },

    /**
     * Check if user is logged in
     * @returns {boolean}
     */
    isLoggedIn() {
      return !!userState;
    },

    /**
     * Get user's current droplets (coins)
     * @returns {number}
     */
    getDroplets() {
      return userState?.droplets || 0;
    },

    /**
     * Get user's current charges
     * @returns {Object} { current, max, cooldownMs, percentage }
     */
    getCharges() {
      return {
        current: userState?.charges?.count || 0,
        max: userState?.charges?.max || 0,
        cooldownMs: userState?.charges?.cooldownMs || 0,
        percentage: userState?.charges?.max > 0
          ? (userState.charges.count / userState.charges.max) * 100
          : 0
      };
    },

    /**
     * Check if user has charges to paint
     * @returns {boolean}
     */
    hasChargesToPaint() {
      return (userState?.charges?.count || 0) >= 1;
    },

    /**
     * Get user's level
     * @returns {number}
     */
    getLevel() {
      return userState?.level || 0;
    },

    /**
     * Get user's total pixels painted
     * @returns {number}
     */
    getPixelsPainted() {
      return userState?.pixelsPainted || 0;
    },

    /**
     * Get user's alliance ID
     * @returns {number|null}
     */
    getAllianceId() {
      return userState?.allianceId || null;
    },

    /**
     * Check if user is in an alliance
     * @returns {boolean}
     */
    isInAlliance() {
      return !!userState?.allianceId;
    },

    /**
     * Add a custom section to the info modal
     * @param {Object} config - Section configuration
     * @param {string} config.id - Unique identifier
     * @param {string} [config.title] - Section title
     * @param {string|HTMLElement|Function} config.content - Section content
     * @param {string} [config.position] - Position: 'top', 'bottom', 'after-video'
     * @param {string} [config.className] - Custom CSS classes
     */
    addInfoSection(config) {
      if (!config.id) {
        console.error('[FurryPlace SDK] Info section must have an id');
        return;
      }

      if (infoModalSections.find(s => s.id === config.id)) {
        console.error('[FurryPlace SDK] Info section with id "' + config.id + '" already registered');
        return;
      }

      infoModalSections.push(config);
      console.log('[FurryPlace SDK] Registered info section:', config.id);

      // Inject immediately if modal is open
      const modalBox = findInfoModal();
      if (modalBox) {
        injectInfoSections();
      }
    },

    /**
     * Remove a custom section from the info modal
     * @param {string} id - Section ID to remove
     */
    removeInfoSection(id) {
      const index = infoModalSections.findIndex(s => s.id === id);
      if (index !== -1) {
        infoModalSections.splice(index, 1);

        // Remove from DOM if present
        const section = document.querySelector('[data-furryplace-info="' + id + '"]');
        if (section) {
          section.remove();
        }

        console.log('[FurryPlace SDK] Removed info section:', id);
      }
    },

    /**
     * Get list of registered info sections
     */
    getInfoSections() {
      return infoModalSections.map(s => ({ id: s.id, title: s.title }));
    },

    /**
     * Add a custom item to the shop
     * @param {Object} config - Shop item configuration
     * @param {string} config.id - Unique identifier
     * @param {string} config.name - Item name
     * @param {string} [config.description] - Item description
     * @param {number} config.price - Price in droplets
     * @param {Function} config.onPurchase - Purchase handler (context, event) => Promise<void>
     * @param {string} [config.position] - Position: 'top', 'bottom'
     * @param {string} [config.className] - Custom CSS classes for item container
     * @param {string} [config.buttonClassName] - Custom CSS classes for button
     * @param {string} [config.buttonText] - Button text (default: 'Purchase')
     * @param {Function} [config.condition] - Optional condition (context) => boolean
     * @param {boolean} [config.disabled=false] - Whether button is disabled
     */
    addShopItem(config) {
      if (!config.id) {
        console.error('[FurryPlace SDK] Shop item must have an id');
        return;
      }

      if (!config.name) {
        console.error('[FurryPlace SDK] Shop item must have a name');
        return;
      }

      if (typeof config.price !== 'number') {
        console.error('[FurryPlace SDK] Shop item must have a numeric price');
        return;
      }

      if (shopItems.find(s => s.id === config.id)) {
        console.error('[FurryPlace SDK] Shop item with id "' + config.id + '" already registered');
        return;
      }

      shopItems.push(config);
      console.log('[FurryPlace SDK] Registered shop item:', config.id);

      // Inject immediately if shop modal is open
      const modalBox = findShopModal();
      if (modalBox) {
        injectShopItems();
      }
    },

    /**
     * Remove a custom item from the shop
     * @param {string} id - Shop item ID to remove
     */
    removeShopItem(id) {
      const index = shopItems.findIndex(s => s.id === id);
      if (index !== -1) {
        shopItems.splice(index, 1);

        // Remove from DOM if present
        const item = document.querySelector('[data-furryplace-shop-item="' + id + '"]');
        if (item) {
          item.remove();
        }

        console.log('[FurryPlace SDK] Removed shop item:', id);
      }
    },

    /**
     * Get list of registered shop items
     */
    getShopItems() {
      return shopItems.map(s => ({ id: s.id, name: s.name, price: s.price }));
    },

    /**
     * Purchase a default store item
     * @param {number} productId - Product ID (70, 80, 100, 110)
     * @param {number} [amount=1] - Amount to purchase
     * @param {number} [variant] - Variant ID (for colors and flags)
     * @returns {Promise<boolean>} True if purchase was successful
     */
    async purchaseStoreItem(productId, amount = 1, variant = null) {
      try {
        const payload = {
          product: {
            id: productId,
            amount: amount
          }
        };

        if (variant !== null) {
          payload.product.variant = variant;
        }

        const response = await fetch('/api/store/purchase', {
          method: 'POST',
          headers: {
            'Content-Type': 'application/json'
          },
          credentials: 'include',
          body: JSON.stringify(payload)
        });

        if (response.ok) {
          const data = await response.json();
          // Refresh user state after purchase
          await refreshUserState();
          return data.success === true;
        }

        return false;
      } catch (error) {
        console.error('[FurryPlace SDK] Purchase failed:', error);
        return false;
      }
    },

    /**
     * Register a captcha callback for a specific verification point
     * @param {string} verificationPoint - One of: 'login', 'register', 'googleOAuth', 'paint'
     * @param {Function} callback - async () => string - Function that returns a captcha token
     */
    registerCaptchaCallback(verificationPoint, callback) {
      if (!['login', 'register', 'googleOAuth', 'paint'].includes(verificationPoint)) {
        console.error('[FurryPlace SDK] Invalid verification point:', verificationPoint);
        return;
      }

      if (typeof callback !== 'function') {
        console.error('[FurryPlace SDK] Captcha callback must be a function');
        return;
      }

      captchaCallbacks[verificationPoint] = callback;
      console.log('[FurryPlace SDK] Registered captcha callback for:', verificationPoint);
    },

    /**
     * Unregister a captcha callback
     * @param {string} verificationPoint - Verification point to remove
     */
    unregisterCaptchaCallback(verificationPoint) {
      delete captchaCallbacks[verificationPoint];
      console.log('[FurryPlace SDK] Unregistered captcha callback for:', verificationPoint);
    },

    /**
     * Request a captcha token for a specific verification point
     * @param {string} verificationPoint - One of: 'login', 'register', 'googleOAuth', 'paint'
     * @returns {Promise<string|null>} Captcha token or null if no callback registered
     */
    async requestCaptcha(verificationPoint) {
      const callback = captchaCallbacks[verificationPoint];
      if (!callback) {
        console.warn('[FurryPlace SDK] No captcha callback registered for:', verificationPoint);
        return null;
      }

      try {
        const token = await callback();
        return token;
      } catch (error) {
        console.error('[FurryPlace SDK] Captcha callback failed:', error);
        return null;
      }
    },

    /**
     * Check if a captcha callback is registered for a verification point
     * @param {string} verificationPoint - Verification point to check
     * @returns {boolean}
     */
    hasCaptchaCallback(verificationPoint) {
      return !!captchaCallbacks[verificationPoint];
    },

    /**
     * Get list of registered captcha callbacks
     * @returns {string[]} Array of verification points with registered callbacks
     */
    getCaptchaCallbacks() {
      return Object.keys(captchaCallbacks);
    },

    /**
     * Get site content (async)
     * @param {string} [locale='en'] - Locale to fetch content for
     * @returns {Promise<Object>} Site content object or null
     */
    async getSiteContent(locale = 'en') {
      return await getSiteContent(locale);
    },

    /**
     * Refresh site content from server
     * @param {string} [locale='en'] - Locale to fetch content for
     * @returns {Promise<Object>} Updated site content or null
     */
    async refreshSiteContent(locale = 'en') {
      return await refreshSiteContent(locale);
    },

    /**
     * Get social links from site content
     * @param {string} [locale='en'] - Locale to fetch content for
     * @returns {Promise<Object>} Social links object
     */
    async getSocialLinks(locale = 'en') {
      const content = await getSiteContent(locale);
      if (!content) return {};

      return {
        twitter: {
          url: content['social.twitter.url'],
          text: content['social.twitter.text']
        },
        bluesky: {
          url: content['social.bluesky.url'],
          text: content['social.bluesky.text']
        },
        discord: {
          url: content['modal.footer.discord.url'],
          text: content['modal.footer.discord.text']
        },
        instagram: {
          url: content['modal.footer.instagram.url'],
          text: content['modal.footer.instagram.text']
        },
        github: {
          url: content['modal.footer.github.url'],
          text: content['modal.footer.github.text']
        }
      };
    }
  };

  // Auto-load plugins from server
  function autoLoadPlugins() {
    console.log('[FurryPlace SDK] Auto-discovering plugins...');

    fetch('/api/plugins')
      .then(response => response.json())
      .then(data => {
        if (!data.plugins || data.plugins.length === 0) {
          console.log('[FurryPlace SDK] No plugins found');
          return;
        }

        console.log('[FurryPlace SDK] Found ' + data.plugins.length + ' plugin(s):', data.plugins.map(p => p.name));

        // Load each plugin dynamically
        data.plugins.forEach(plugin => {
          const script = document.createElement('script');
          script.src = plugin.path;
          script.async = true;
          script.onerror = () => {
            console.error('[FurryPlace SDK] Failed to load plugin:', plugin.name);
          };
          script.onload = () => {
            console.log('[FurryPlace SDK] Loaded plugin:', plugin.name);
          };
          document.head.appendChild(script);
        });
      })
      .catch(error => {
        console.warn('[FurryPlace SDK] Failed to auto-discover plugins:', error.message);
      });
  }

  // Auto-initialize when DOM is ready
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', () => {
      initialize();
      autoLoadPlugins();
    });
  } else {
    initialize();
    autoLoadPlugins();
  }

  console.log('[FurryPlace SDK] Loaded v' + SDK_VERSION);
})();
