Source: market/v2/models/Summary.js

/**
 * Summary model for v2 API
 * Compatible with v1 Summary but with v2 data structures
 */

import { ASSETS_BASE_URL } from '../constants.js';

export default class SummaryV2 {
  /**
   * Create a summary from v2 Item and Order data
   * @param {Item} item - Item object
   * @param {Object} orders - Orders object with { buy, sell }
   * @param {Object} statistics - Statistics object with { buy, sell }
   */
  constructor(item, orders, statistics) {
    // Basic item info
    this.name = item.name;
    this.slug = item.slug;
    this.thumbnail = item.getThumbUrl(ASSETS_BASE_URL);
    this.partThumb = item.subIcon ? `${ASSETS_BASE_URL}${item.subIcon}` : this.thumbnail;
    this.icon = item.getIconUrl(ASSETS_BASE_URL);

    // Item metadata
    this.tradingTax = item.tradingTax || 0;
    this.ducats = item.ducats || 0;
    this.vosfor = item.vosfor || 0;
    this.masteryLevel = item.reqMasteryRank;
    this.tradable = item.tradable;
    this.vaulted = item.vaulted;

    // Links and descriptions
    this.wikiUrl = item.wikiLink;
    this.description = item.description || '';
    this.codex = this.description; // v1 compatibility
    this.url = `https://warframe.market/items/${item.slug}`;

    // Tags
    this.tags = item.tags || [];

    // Prices and statistics (from v2 orders)
    this.prices = {
      soldCount: statistics.sell.orderCount,
      soldPrice: statistics.sell.median,
      minimum: statistics.sell.min,
      maximum: statistics.sell.max,
      average: statistics.sell.avg,
      volume: statistics.sell.volume,
    };

    // Store full orders and statistics
    this.orders = orders;
    this.statistics = statistics;

    // Type marker
    this.type = 'market-v2';
  }

  /**
   * Get best online sellers
   * @param {number} limit - Number of sellers to return
   * @returns {Array<{ingameName: string, platinum: number, quantity: number, status: string, activity: string}>}
   */
  getOnlineSellers(limit = 5) {
    return this.orders.sell
      .filter((order) => order.isUserOnline())
      .slice(0, limit)
      .map((order) => ({
        ingameName: order.user.ingameName,
        platinum: order.platinum,
        quantity: order.quantity,
        status: order.user.status,
        activity: order.user.getActivityDescription(),
        reputation: order.user.reputation,
        platform: order.user.platform,
        crossplay: order.user.crossplay,
      }));
  }

  /**
   * Get best online buyers
   * @param {number} limit - Number of buyers to return
   * @returns {Array<{ingameName: string, platinum: number, quantity: number, status: string, activity: string}>}
   */
  getOnlineBuyers(limit = 5) {
    return this.orders.buy
      .filter((order) => order.isUserOnline())
      .slice(0, limit)
      .map((order) => ({
        ingameName: order.user.ingameName,
        platinum: order.platinum,
        quantity: order.quantity,
        status: order.user.status,
        activity: order.user.getActivityDescription(),
        reputation: order.user.reputation,
        platform: order.user.platform,
        crossplay: order.user.crossplay,
      }));
  }

  /**
   * String representation (v1 compatible)
   * @param {string} opt - Option: 'codex', 'item', 'location', 'all'
   * @returns {string}
   */
  toString(opt = 'item') {
    let value = `**${this.name}**\n`;

    if (opt === 'codex' || opt === 'all') {
      value += `\`\`\`\nCodex: ${this.codex}\n\`\`\`\n`;
    }

    if (opt === 'item' || opt === 'all') {
      value += '```\n';
      if (this.masteryLevel) {
        value += `Requires: MR ${this.masteryLevel} | `;
      }
      value += `Tax: ${this.tradingTax}cr\n`;
      value += `Item is ${this.tradable ? '' : 'not '}tradable`;
      if (this.vaulted) {
        value += ' (VAULTED)';
      }
      value += '\n\n';

      // Sell statistics
      value += `${this.prices.soldCount} sellers (${this.prices.volume} items)\n`;
      value += `Median: ${this.prices.soldPrice}p | `;
      value += `Range: ${this.prices.minimum}p - ${this.prices.maximum}p\n`;

      // Buy statistics (if available)
      if (this.statistics.buy.orderCount > 0) {
        value += `\n${this.statistics.buy.orderCount} buyers\n`;
        value += `Median: ${this.statistics.buy.median}p | `;
        value += `Range: ${this.statistics.buy.min}p - ${this.statistics.buy.max}p\n`;
      }

      value += '```\n';
    }

    if (opt === 'traders' || opt === 'all') {
      const sellers = this.getOnlineSellers(3);
      if (sellers.length > 0) {
        value += '\n**Online Sellers:**\n';
        sellers.forEach((seller) => {
          const statusEmoji = seller.status === 'ingame' ? '🎮' : '🟢';
          const activity = seller.activity ? ` (${seller.activity})` : '';
          value += `${statusEmoji} ${seller.ingameName} - ${seller.platinum}p x${seller.quantity}${activity}\n`;
        });
      }
    }

    value += `\n${this.wikiUrl || this.url}`;
    return value;
  }

  /**
   * Padding helper (v1 compatibility)
   * @param {string} str - String to pad
   * @param {number} length - Target length
   * @param {string} character - Padding character
   * @returns {string}
   */
  pad(str, length = 10, character = ' ') {
    return str.padEnd(length, character);
  }
}