/**
 * PlayerProfile.js
 * Manages the player's public profile (nickname + avatar url) for the game.
 *
 * Acquisition strategy (WeChat mini-game, compliant, 2024+):
 *   1. Try to read nickname from local storage cache (`playerProfile`).
 *   2. If absent, try `wx.getUserInfo({ withCredentials: false })` — this does NOT
 *      require an authorization popup since 2022 and returns an anonymous
 *      "微信用户" placeholder + default avatar. That is acceptable as the
 *      silent fallback.
 *   3. The caller (MenuScene) can additionally create a `UserInfoButton`
 *      and register a click handler via `bindUserInfoButton()` below to
 *      upgrade the placeholder into the real nickname when the user taps
 *      the overlay button.
 *
 * Display helpers:
 *   - `getDisplayName()`  — returns a "safe" display string (nickname if set,
 *                           otherwise "坦克手_XXXX" derived from playerId).
 *   - `truncate(name, n)` — truncate at n Chinese-equivalent characters.
 */

const STORAGE_KEY = 'playerProfile';

class PlayerProfile {
  constructor() {
    /** @type {string} Real WeChat nickname, '' if not yet granted. */
    this._nickname = '';
    /** @type {string} Avatar URL, '' if not yet granted. */
    this._avatarUrl = '';
    /** @type {boolean} Whether we have attempted a silent fetch at least once. */
    this._silentFetched = false;
    /** @type {boolean} Whether the real (non-anonymous) nickname has been granted. */
    this._granted = false;

    this._loadFromCache();
  }

  // ============================================================
  // Public API
  // ============================================================

  /** @returns {string} the cached nickname, or '' if never set. */
  get nickname() {
    return this._nickname;
  }

  /** @returns {string} avatar URL or '' */
  get avatarUrl() {
    return this._avatarUrl;
  }

  /** @returns {boolean} whether the user has granted the real nickname. */
  get granted() {
    return this._granted;
  }

  /**
   * Build a safe display name for UI rendering.
   * Order of preference: real nickname > anonymous from last silent fetch
   *   > deterministic "Tanker_XXXX" fallback based on playerId.
   * @param {string} [playerIdFallback] - optional player id for deterministic fallback.
   * @returns {string}
   */
  getDisplayName(playerIdFallback) {
    if (this._nickname) return this._nickname;
    if (playerIdFallback && typeof playerIdFallback === 'string') {
      // Use the last 4 chars of playerId for a stable anonymous tag.
      const tail = playerIdFallback.slice(-4).toUpperCase();
      return `Tanker_${tail}`;
    }
    return 'Tanker';
  }

  /**
   * Truncate a display name to at most `maxChineseChars` Chinese-equivalent chars.
   * A Chinese char counts as 1; a latin char counts as 0.5.
   * @param {string} name
   * @param {number} [maxChineseChars=4]
   * @returns {string}
   */
  truncate(name, maxChineseChars = 4) {
    if (!name) return '';
    let widthBudget = maxChineseChars * 2; // in half-width units
    let out = '';
    for (let i = 0; i < name.length; i++) {
      const ch = name.charAt(i);
      const code = name.charCodeAt(i);
      // Treat CJK + Full-width chars as 2 half-width units, others as 1.
      const w = code > 0x7f ? 2 : 1;
      if (widthBudget - w < 0) {
        return out + '..';
      }
      widthBudget -= w;
      out += ch;
    }
    return out;
  }

  /**
   * Fetch the user's nickname + avatar following the official WeChat flow:
   *
   *   1. `wx.getSetting` → check `scope.userInfo`
   *   2. Already authorized → `wx.getUserInfo` to get profile directly
   *   3. Not authorized → `wx.createUserInfoButton` overlay, wait for tap
   *
   * @param {object} [layout] - Position/size for the UserInfoButton overlay.
   *   { x, y, width, height } in CSS pixels (logical pixels).
   *   If omitted, a default area in the top-left corner is used.
   * @returns {Promise<boolean>} true if a real (non-placeholder) nickname was set.
   */
  fetchSilent(layout) {
    return new Promise((resolve) => {
      if (this._granted) {
        resolve(true);
        return;
      }

      try {
        if (typeof wx === 'undefined') {
          resolve(false);
          return;
        }

        // ── Step 1: wx.getSetting — check if scope.userInfo is already granted ──
        if (typeof wx.getSetting === 'function') {
          console.log('[PlayerProfile] Checking auth setting via wx.getSetting...');
          wx.getSetting({
            success: (settingRes) => {
              const authorized = settingRes && settingRes.authSetting && settingRes.authSetting['scope.userInfo'];
              console.log('[PlayerProfile] getSetting result: scope.userInfo =', authorized);

              if (authorized) {
                // ── Step 2: Already authorized → getUserInfo directly ──
                this._fetchViaGetUserInfo(resolve);
              } else {
                // ── Step 3: Not authorized → create UserInfoButton ──
                this._createUserInfoButton(layout, resolve);
              }
            },
            fail: (err) => {
              console.warn('[PlayerProfile] wx.getSetting fail:', err && err.errMsg, '— falling back to UserInfoButton');
              // Can't determine auth status — create the button as a safe default
              this._createUserInfoButton(layout, resolve);
            },
          });
          return;
        }

        // ── Fallback: no wx.getSetting → try UserInfoButton, then getUserInfo ──
        if (typeof wx.createUserInfoButton === 'function') {
          this._createUserInfoButton(layout, resolve);
          return;
        }

        if (typeof wx.getUserInfo === 'function') {
          this._fetchViaGetUserInfo(resolve);
          return;
        }

        resolve(false);
      } catch (e) {
        console.warn('[PlayerProfile] fetchSilent error:', e && e.message);
        resolve(false);
      }
    });
  }

  /**
   * Create a transparent UserInfoButton overlay and wait for user tap.
   * This is the ONLY way to acquire real profile data from a user who
   * has not yet granted scope.userInfo.
   * @param {object} [layout] - { x, y, width, height }
   * @param {Function} resolve - Promise resolver
   * @private
   */
  _createUserInfoButton(layout, resolve) {
    if (typeof wx.createUserInfoButton !== 'function') {
      resolve(false);
      return;
    }

    console.log('[PlayerProfile] Creating UserInfoButton (scope not authorized)...');
    const btnLayout = layout || { x: 10, y: 10, width: 120, height: 32 };

    const button = wx.createUserInfoButton({
      type: 'text',
      text: '',
      style: {
        left: btnLayout.x,
        top: btnLayout.y,
        width: btnLayout.width,
        height: btnLayout.height,
        backgroundColor: 'transparent',
        borderColor: 'transparent',
        color: 'transparent',
        fontSize: 1,
        borderRadius: 0,
        textAlign: 'center',
        lineHeight: btnLayout.height,
      },
    });

    button.onTap((res) => {
      console.log('[PlayerProfile] UserInfoButton onTap:',
        res && res.userInfo ? { nickName: res.userInfo.nickName, hasAvatar: !!res.userInfo.avatarUrl }
          : (res && res.errMsg ? { errMsg: res.errMsg } : 'null'));

      const u = res && res.userInfo;
      if (!u || !u.nickName || this.isPlaceholderName(u.nickName)) {
        console.log('[PlayerProfile] UserInfoButton returned placeholder or no data — keeping button for retry');
        if (u && u.avatarUrl && !this._avatarUrl) {
          this._avatarUrl = u.avatarUrl;
          this._saveToCache();
          this._emitProfileUpdate();
        }
        // Don't destroy the button — user may tap again later.
        if (!this._granted) resolve(false);
        return;
      }

      // Success — destroy the button, we don't need it anymore
      try { button.destroy(); } catch (e) { /* ignore */ }
      this._userInfoButton = null;

      this._nickname = u.nickName;
      this._avatarUrl = u.avatarUrl || '';
      this._granted = true;
      this._silentFetched = true;
      this._saveToCache();
      this._emitProfileUpdate();
      console.log('[PlayerProfile] Profile granted via UserInfoButton:', this._nickname);
      resolve(true);
    });

    button.show();

    // Store button ref so MenuScene can destroy it on exit
    this._userInfoButton = button;

    // Timeout: if user never taps, unblock the Promise but keep the button visible
    setTimeout(() => {
      if (!this._granted && this._userInfoButton === button) {
        console.log('[PlayerProfile] UserInfoButton not tapped after 30s — resolving false, button stays visible');
        resolve(false);
      }
    }, 30000);
  }

  /**
   * Deprecated fallback: call wx.getUserInfo.
   * On modern WeChat (2022-10+) this returns "scope unauthorized" and
   * should NOT be used as the primary acquisition path.
   * @param {Function} resolve
   * @private
   */
  _fetchViaGetUserInfo(resolve) {
    if (this._silentFetched) {
      resolve(!!this._nickname);
      return;
    }
    this._silentFetched = true;

    let resolved = false;
    console.log('[PlayerProfile] Calling wx.getUserInfo (deprecated fallback)...');
    wx.getUserInfo({
      withCredentials: false,
      success: (res) => {
        if (resolved) return;
        resolved = true;
        const u = res && res.userInfo;
        console.log('[PlayerProfile] wx.getUserInfo success, userInfo:', u ? { nickName: u.nickName, hasAvatar: !!u.avatarUrl } : 'null');
        if (u) {
          let changed = false;
          if (u.nickName && u.nickName !== this._nickname && !this.isPlaceholderName(u.nickName)) {
            this._nickname = u.nickName;
            this._granted = true;
            changed = true;
          }
          if (u.avatarUrl && u.avatarUrl !== this._avatarUrl) {
            this._avatarUrl = u.avatarUrl;
            changed = true;
          }
          if (changed) {
            console.log('[PlayerProfile] Profile updated from getUserInfo');
            this._saveToCache();
            this._emitProfileUpdate();
          }
          resolve(!!this._nickname);
          return;
        }
        resolve(false);
      },
      fail: (err) => {
        if (resolved) return;
        resolved = true;
        console.warn('[PlayerProfile] wx.getUserInfo fail:', err && err.errMsg);
        resolve(false);
      },
    });

    // Timeout guard
    setTimeout(() => {
      if (!resolved) {
        resolved = true;
        console.warn('[PlayerProfile] wx.getUserInfo timed out after 5s');
        resolve(false);
      }
    }, 5000);
  }

  /**
   * Destroy the UserInfoButton overlay if one exists.
   * Call this when leaving the MenuScene or when the profile is granted.
   */
  destroyUserInfoButton() {
    if (this._userInfoButton) {
      try {
        this._userInfoButton.destroy();
      } catch (e) { /* ignore */ }
      this._userInfoButton = null;
    }
  }

  /**
   * Detect the well-known WeChat anonymous placeholder. Since 2022-10,
   * `wx.getUserInfo` / `UserInfoButton` return this string for any user who
   * has not explicitly granted profile access — it must NOT be promoted to
   * the "granted" state.
   * @param {string} name
   * @returns {boolean}
   */
  isPlaceholderName(name) {
    if (!name) return true;
    return name === '微信用户' || name === 'WeChat User' || name === 'Weixin User';
  }

  /**
   * Handle the result of a `UserInfoButton` tap. Should be wired in by
   * whichever scene created the button (usually MenuScene).
   * @param {object} res - the `res` payload passed into the button's onTap callback.
   * @returns {boolean} true if a REAL (non-placeholder) nickname was stored.
   */
  applyUserInfoResult(res) {
    const u = res && res.userInfo;
    if (!u || !u.nickName) return false;

    // Reject WeChat's anonymous placeholder — it's what `getUserInfo` now
    // returns for non-granted users and we must not mark that as "granted".
    if (this.isPlaceholderName(u.nickName)) {
      console.log('[PlayerProfile] Ignored placeholder nickname from UserInfoButton.');
      return false;
    }

    this._nickname = u.nickName;
    this._avatarUrl = u.avatarUrl || '';
    this._granted = true;
    this._saveToCache();
    this._emitProfileUpdate();
    console.log('[PlayerProfile] Nickname granted:', this._nickname);
    return true;
  }

  /**
   * Active authorization flow for WeChat mini-GAMES.
   *
   * Since 2022-10 `wx.createUserInfoButton` silently returns the "微信用户"
   * placeholder, so the ONLY API that still pops a real authorization UI and
   * returns the user's actual WeChat nickname on small-game runtimes is
   * `wx.getUserProfile`. This call MUST be triggered directly by a user tap
   * (a touchend / click handler) — not by any async continuation — or WeChat
   * will reject it with `fail api scope is not declared in the privacy agreement`.
   *
   * @returns {Promise<boolean>} true iff a real nickname was granted.
   */
  requestUserProfile() {
    return new Promise((resolve) => {
      try {
        if (typeof wx === 'undefined' || typeof wx.getUserProfile !== 'function') {
          resolve(false);
          return;
        }

        const doProfile = () => {
          wx.getUserProfile({
            desc: '用于在对战中展示你的昵称',
            lang: 'zh_CN',
            success: (res) => {
              const u = res && res.userInfo;
              if (!u || !u.nickName || this.isPlaceholderName(u.nickName)) {
                console.log('[PlayerProfile] getUserProfile returned placeholder.');
                resolve(false);
                return;
              }
              this._nickname = u.nickName;
              this._avatarUrl = u.avatarUrl || '';
              this._granted = true;
              this._saveToCache();
              this._emitProfileUpdate();
              console.log('[PlayerProfile] Nickname granted via getUserProfile:', this._nickname);
              resolve(true);
            },
            fail: (err) => {
              console.log('[PlayerProfile] getUserProfile fail:', err && err.errMsg);
              resolve(false);
            },
          });
        };

        // Call wx.getUserProfile directly. If the user hasn't authorized
        // privacy yet, WeChat will trigger wx.onNeedPrivacyAuthorization
        // (registered in game.js) which shows our PrivacyPopup, then
        // automatically retries the pending API call after the user agrees.
        //
        // We do NOT call wx.requirePrivacyAuthorize because it is unreliable
        // in WeChat mini-games — see fetchSilent() comments for details.
        doProfile();
      } catch (e) {
        console.warn('[PlayerProfile] requestUserProfile error:', e && e.message);
        resolve(false);
      }
    });
  }

  /**
   * Manual nickname fallback — used when `getUserProfile` is unavailable or
   * denied (e.g. user refused, API deprecated). Stores the user-typed name
   * and marks the profile as "granted" so the button stops prompting.
   * @param {string} name
   * @returns {boolean}
   */
  setManualNickname(name) {
    if (!name || typeof name !== 'string') return false;
    const trimmed = name.trim();
    if (!trimmed || this.isPlaceholderName(trimmed)) return false;
    this._nickname = trimmed.slice(0, 16); // hard cap to 16 chars
    this._granted = true;
    this._saveToCache();
    console.log('[PlayerProfile] Nickname set manually:', this._nickname);
    return true;
  }

  /**
   * Clear the cached profile (e.g. user wants to re-auth).
   */
  reset() {
    this._nickname = '';
    this._avatarUrl = '';
    this._granted = false;
    try { wx.removeStorageSync(STORAGE_KEY); } catch (e) { /* ignore */ }
  }

  // ============================================================
  // Private
  // ============================================================

  _loadFromCache() {
    try {
      const raw = wx.getStorageSync(STORAGE_KEY);
      if (raw && typeof raw === 'object') {
        this._nickname = raw.nickname || '';
        this._avatarUrl = raw.avatarUrl || '';
        this._granted = !!raw.granted;
      }
    } catch (e) {
      // Ignore storage errors.
    }
  }

  _saveToCache() {
    try {
      wx.setStorageSync(STORAGE_KEY, {
        nickname: this._nickname,
        avatarUrl: this._avatarUrl,
        granted: this._granted,
      });
    } catch (e) {
      // Ignore storage errors.
    }
  }

  /**
   * Emit a `profile:updated` event via the global EventBus so that
   * other systems (e.g. NetworkManager, TeamRoomScene) can react to
   * nickname / avatarUrl changes — typically by re-sending the latest
   * profile data to the server so peers see the updated avatar.
   * @private
   */
  _emitProfileUpdate() {
    try {
      const bus = (typeof GameGlobal !== 'undefined') ? GameGlobal.eventBus : null;
      if (bus && typeof bus.emit === 'function') {
        bus.emit('profile:updated', {
          nickname: this._nickname,
          avatarUrl: this._avatarUrl,
          granted: this._granted,
        });
      }
    } catch (e) {
      // Non-critical — event emission should never break the game.
    }
  }
}

module.exports = PlayerProfile;
