/**
 * User 状态机：纯函数 + 数据加载
 *
 * 从 `user-machine.ts` 抽出的"Helpers"段：
 *   - InitData 形状
 *   - `toView`：DTO → UserView 映射（纯函数）
 *   - `readInitData`：从 UserStorage 读 user + avatarUrl（并行）
 *   - `userStorage` 单例：被 helpers 和 actors 共用
 *
 * 设计目标：
 *   - helpers 无 XState 依赖（可独立 import / 测试）
 *   - actors 依赖 helpers（import）
 *   - machine 依赖 actors（import）—— 不直接依赖 helpers
 */

import type { UserView } from "@/data/dto/user";
import type { UserEntitlementSnapshotData } from "@/data/schemas/user";
import { UserStorage } from "@/data/storage/user/user_storage";
import { Result } from "@/utils";

// ============================================================
// Storage singleton
// ============================================================
/**
 * UserStorage 单例 —— 被 helpers（readInitData）和 actors（userFetchActor / userLogoutActor）
 * 共同使用。从 helpers 导出，避免在 actors 里重复 `UserStorage.getInstance()`。
 */
export const userStorage = UserStorage.getInstance();

// ============================================================
// InitData 形状
// ============================================================
/**
 * `userInitActor` 的输出形状（user-machine.initializing.onDone 用）——
 * 拆分 user 和 avatarUrl 是因为它们来自 UserStorage 两个独立接口。
 */
export interface InitData {
  user: UserView | null;
  avatarUrl: string | null;
  snapshot: UserEntitlementSnapshotData | null;
}

export interface UserFetchData {
  user: UserView | null;
  snapshot: UserEntitlementSnapshotData | null;
}

// ============================================================
// DTO → UserView 映射
// ============================================================
/**
 * UserView DTO → UserView 视图模型（纯函数，可单测）
 * - 与 chat 的 `localMessagesToUi` 同模式
 * - 默认值收敛在此：undefined → "" / 0 / false，避免下游到处判空
 */
export function toView(u: {
  id: string;
  username: string;
  email?: string;
  country?: string;
  countryCode?: string;
  avatarUrl?: string;
  intimacy?: number;
  dolBalance?: number;
  creditBalance?: number;
  vipExpiresAt?: string | null;
  relationshipStage?: string;
  currentMood?: string;
  isGuest?: boolean;
  isVip?: boolean;
  voiceMinutesRemaining?: number;
}): UserView {
  const creditBalance = u.creditBalance ?? u.dolBalance ?? 0;

  return {
    id: u.id,
    username: u.username,
    email: u.email ?? "",
    country: u.country ?? "",
    countryCode: u.countryCode ?? "",
    avatarUrl: u.avatarUrl ?? "",
    intimacy: u.intimacy ?? 0,
    dolBalance: u.dolBalance ?? creditBalance,
    creditBalance,
    vipExpiresAt: u.vipExpiresAt ?? null,
    relationshipStage: u.relationshipStage ?? "",
    currentMood: u.currentMood ?? "",
    isGuest: u.isGuest ?? false,
    isVip: u.isVip ?? false,
    voiceMinutesRemaining: u.voiceMinutesRemaining ?? 0,
  };
}

export function applyEntitlementSnapshotToView(
  user: UserView | null,
  snapshot: UserEntitlementSnapshotData | null,
): UserView | null {
  if (!user || !snapshot) return user;
  return {
    ...user,
    isVip: snapshot.isVip,
    dolBalance: snapshot.creditBalance,
    creditBalance: snapshot.creditBalance,
  };
}

export function toEntitlementSnapshot(input: {
  isVip: boolean;
  creditBalance: number;
}): UserEntitlementSnapshotData {
  return {
    isVip: input.isVip,
    creditBalance: input.creditBalance,
  };
}

// ============================================================
// Init 数据加载（userInitActor 用）
// ============================================================
/**
 * 并行读 user + avatarUrl（UserStorage 两个独立接口）。
 * - 任意一个失败 / 无数据 → 对应字段返回 null，不抛错（与 chat 的
 *   `readAndSyncHistory` "失败不卡 init" 一致的设计）
 * - Result.isOk(_) && data 双重判空：Result 包装层 + 业务 null
 */
export async function readInitData(): Promise<InitData> {
  const [userResult, avatarResult, snapshotResult] = await Promise.all([
    userStorage.getUser(),
    userStorage.getAvatarUrl(),
    userStorage.getEntitlementSnapshot(),
  ]);

  let snapshot: UserEntitlementSnapshotData | null = null;
  if (Result.isOk(snapshotResult) && snapshotResult.data) {
    snapshot = snapshotResult.data;
  }

  let user: UserView | null = null;
  if (Result.isOk(userResult) && userResult.data) {
    user = applyEntitlementSnapshotToView(toView(userResult.data), snapshot);
  }

  let avatarUrl: string | null = null;
  if (Result.isOk(avatarResult) && avatarResult.data && avatarResult.data.length > 0) {
    avatarUrl = avatarResult.data;
  }

  return { user, avatarUrl, snapshot };
}
