import { API_ENDPOINTS, API_OPERATIONS, apiEndpointPath } from "@/shared/api/generated/endpoints"; import { apiRequest } from "@/shared/api/request"; import type { EntityId, PageQuery } from "@/shared/api/types"; export type LevelTrackCode = "wealth" | "game" | "charm"; export type LevelConfigStatus = "active" | "disabled"; export interface LevelRuleDto { createdAtMs?: number; displayConfigJson?: string; level: number; avatarFrameResourceId?: number; longBadgeResourceId?: number; name?: string; requiredValue: number; rewardResourceGroupId?: number; shortBadgeResourceId?: number; sortOrder?: number; status?: LevelConfigStatus | string; track: LevelTrackCode; updatedAtMs?: number; } export interface LevelTierDto { createdAtMs?: number; displayAvatarFrameResourceId?: number; displayBadgeResourceId?: number; displayConfigJson?: string; maxLevel: number; minLevel: number; name?: string; rewardResourceGroupId?: number; status?: LevelConfigStatus | string; tierId: number; track: LevelTrackCode; updatedAtMs?: number; } export interface LevelTrackDto { createdAtMs?: number; displayConfigJson?: string; name?: string; rules: LevelRuleDto[]; sortOrder?: number; status?: LevelConfigStatus | string; tiers: LevelTierDto[]; track: LevelTrackCode; updatedAtMs?: number; } export interface LevelConfigDto { serverTimeMs?: number; tracks: LevelTrackDto[]; } export interface LevelTrackPayload { display_config_json?: string; name: string; sort_order: number; status: LevelConfigStatus; } export interface LevelRulePayload { avatar_frame_resource_id?: number; long_badge_resource_id?: number; name: string; required_value: number; reward_resource_group_id?: number; short_badge_resource_id?: number; sort_order: number; status: LevelConfigStatus; } export interface LevelTierPayload { display_avatar_frame_resource_id?: number; display_badge_resource_id?: number; display_config_json?: string; max_level: number; min_level: number; name: string; reward_resource_group_id?: number; status: LevelConfigStatus; track: LevelTrackCode; } type AnyRecord = Record; export function listLevelConfig(query: PageQuery = {}): Promise { const endpoint = API_ENDPOINTS.listLevelConfig; return apiRequest(apiEndpointPath(API_OPERATIONS.listLevelConfig), { method: endpoint.method, query, }).then(normalizeLevelConfig); } export function updateTrack(track: LevelTrackCode, payload: LevelTrackPayload): Promise { const endpoint = API_ENDPOINTS.updateTrack; return apiRequest(apiEndpointPath(API_OPERATIONS.updateTrack, { track }), { body: payload, method: endpoint.method, }).then(normalizeTrack); } export function upsertRule( track: LevelTrackCode, level: EntityId, payload: LevelRulePayload, ): Promise { const endpoint = API_ENDPOINTS.upsertRule; return apiRequest( apiEndpointPath(API_OPERATIONS.upsertRule, { level, track }), { body: payload, method: endpoint.method, }, ).then(normalizeRule); } export function createTier(payload: LevelTierPayload): Promise { const endpoint = API_ENDPOINTS.createTier; return apiRequest(apiEndpointPath(API_OPERATIONS.createTier), { body: payload, method: endpoint.method, }).then(normalizeTier); } export function updateTier(tierId: EntityId, payload: LevelTierPayload): Promise { const endpoint = API_ENDPOINTS.updateTier; return apiRequest(apiEndpointPath(API_OPERATIONS.updateTier, { tier_id: tierId }), { body: payload, method: endpoint.method, }).then(normalizeTier); } function normalizeLevelConfig(raw: unknown): LevelConfigDto { const source = asRecord(raw); return { serverTimeMs: numberValue(source.serverTimeMs ?? source.server_time_ms), tracks: arrayValue(source.tracks).map(normalizeTrack), }; } function normalizeTrack(raw: unknown): LevelTrackDto { const source = asRecord(raw); return { createdAtMs: numberValue(source.createdAtMs ?? source.created_at_ms), displayConfigJson: jsonString(source.displayConfigJson ?? source.display_config_json), name: stringValue(source.name), rules: arrayValue(source.rules).map(normalizeRule), sortOrder: numberValue(source.sortOrder ?? source.sort_order), status: stringValue(source.status), tiers: arrayValue(source.tiers).map(normalizeTier), track: stringValue(source.track) as LevelTrackCode, updatedAtMs: numberValue(source.updatedAtMs ?? source.updated_at_ms), }; } function normalizeRule(raw: unknown): LevelRuleDto { const source = asRecord(raw); const displayConfig = source.displayConfigJson ?? source.display_config_json; const displayConfigObject = parseDisplayConfig(displayConfig); return { createdAtMs: numberValue(source.createdAtMs ?? source.created_at_ms), displayConfigJson: jsonString(displayConfig), level: numberValue(source.level), avatarFrameResourceId: numberValue( source.avatarFrameResourceId ?? source.avatar_frame_resource_id ?? displayConfigObject.avatarFrameResourceId ?? displayConfigObject.avatar_frame_resource_id, ), longBadgeResourceId: numberValue( source.longBadgeResourceId ?? source.long_badge_resource_id ?? displayConfigObject.longBadgeResourceId ?? displayConfigObject.long_badge_resource_id, ), name: stringValue(source.name), requiredValue: numberValue(source.requiredValue ?? source.required_value), rewardResourceGroupId: numberValue(source.rewardResourceGroupId ?? source.reward_resource_group_id), shortBadgeResourceId: numberValue( source.shortBadgeResourceId ?? source.short_badge_resource_id ?? displayConfigObject.shortBadgeResourceId ?? displayConfigObject.short_badge_resource_id, ), sortOrder: numberValue(source.sortOrder ?? source.sort_order), status: stringValue(source.status), track: stringValue(source.track) as LevelTrackCode, updatedAtMs: numberValue(source.updatedAtMs ?? source.updated_at_ms), }; } function normalizeTier(raw: unknown): LevelTierDto { const source = asRecord(raw); return { createdAtMs: numberValue(source.createdAtMs ?? source.created_at_ms), displayAvatarFrameResourceId: numberValue( source.displayAvatarFrameResourceId ?? source.display_avatar_frame_resource_id, ), displayBadgeResourceId: numberValue(source.displayBadgeResourceId ?? source.display_badge_resource_id), displayConfigJson: jsonString(source.displayConfigJson ?? source.display_config_json), maxLevel: numberValue(source.maxLevel ?? source.max_level), minLevel: numberValue(source.minLevel ?? source.min_level), name: stringValue(source.name), rewardResourceGroupId: numberValue(source.rewardResourceGroupId ?? source.reward_resource_group_id), status: stringValue(source.status), tierId: numberValue(source.tierId ?? source.tier_id), track: stringValue(source.track) as LevelTrackCode, updatedAtMs: numberValue(source.updatedAtMs ?? source.updated_at_ms), }; } function asRecord(value: unknown): AnyRecord { return value && typeof value === "object" && !Array.isArray(value) ? (value as AnyRecord) : {}; } function arrayValue(value: unknown): unknown[] { return Array.isArray(value) ? value : []; } function numberValue(value: unknown): number { if (value === undefined || value === null || value === "") { return 0; } const numeric = Number(value); return Number.isFinite(numeric) ? numeric : 0; } function stringValue(value: unknown): string { return value === undefined || value === null ? "" : String(value); } function jsonString(value: unknown): string { if (value === undefined || value === null || value === "") { return ""; } if (typeof value === "string") { return value; } return JSON.stringify(value); } function parseDisplayConfig(value: unknown): AnyRecord { const raw = jsonString(value).trim(); if (!raw) { return {}; } try { return asRecord(JSON.parse(raw)); } catch { return {}; } }