import { create } from 'zustand';
import { persist, createJSONStorage } from 'zustand/middleware';
import recipesData from '../data/recipes.json';
import elementsData from '../data/elements.json';
import prestigeTableData from '../data/prestige.json';
import achievementsData from '../data/achievements.json';

// localStorage 저장을 최대 10초에 1번으로 스로틀링
// 매 tick(1초)마다 setState가 발생하는 방치형 게임 특성상 필요
const STORAGE_SAVE_INTERVAL_MS = 10_000;

let _pendingStorageKey: string | null = null;
let _pendingStorageValue: string | null = null;
let _storageTimer: ReturnType<typeof setTimeout> | null = null;

function _doFlush() {
  if (_pendingStorageKey !== null && _pendingStorageValue !== null) {
    localStorage.setItem(_pendingStorageKey, _pendingStorageValue);
    _pendingStorageKey = null;
    _pendingStorageValue = null;
  }
  _storageTimer = null;
}

/** 탭 전환/앱 종료 직전 즉시 저장 (beforeunload에서 호출) */
export function flushGameState(): void {
  if (_storageTimer !== null) {
    clearTimeout(_storageTimer);
    _storageTimer = null;
  }
  _doFlush();
}

const throttledStorage = createJSONStorage(() => ({
  getItem: (name: string): string | null => localStorage.getItem(name),
  setItem: (name: string, value: string): void => {
    _pendingStorageKey = name;
    _pendingStorageValue = value;
    if (_storageTimer === null) {
      _storageTimer = setTimeout(_doFlush, STORAGE_SAVE_INTERVAL_MS);
    }
  },
  removeItem: (name: string): void => localStorage.removeItem(name),
}));

export type TabName = 'elements' | 'evolution' | 'fusion' | 'shop' | 'achievements' | 'settings';
export type Language = 'ko' | 'en';

const INITIAL_ELEMENTS: Record<string, number> = {
  fire: 5,
  water: 5,
  wind: 5,
  earth: 5,
};
const INITIAL_DISCOVERED_ELEMENT_IDS = Object.keys(INITIAL_ELEMENTS);

export const ENHANCE_MAX_LEVEL = 5;
const ENHANCE_BASE_COST = 50;
const ENHANCE_COST_MULTIPLIER = 1.5;
const ENHANCE_RATE_MULTIPLIER = 1.2;
const SPAWN_ENHANCE_MULTIPLIER = 1.2;
const MAX_OFFLINE_SECONDS = 86400; // 24시간
const MAX_RESONANCE_SPAWN_BONUS = 2.0;
const MAX_RESONANCE_GOLD_BONUS = 3.0;
const TIER_SPAWN_RATE_MULTIPLIER: Record<number, number> = {
  1: 1.0,
  2: 0.12,
  3: 0.025,
  4: 0.004,
  5: 0.0007,
};
const COMBO_WINDOW_MS = 8_000;
const LUCKY_FUSION_RATE = 0.01;
const STORM_INTERVAL_MS = 6 * 60 * 60 * 1000;
const STORM_DURATION_MS = 5 * 60 * 1000;
const TAP_HARVEST_THRESHOLD = 0.75;
const TAP_HARVEST_BONUS = 1.5;

export type DailyMissionType = 'fuse' | 'collect' | 'discover';

export interface DailyMissionDefinition {
  id: string;
  type: DailyMissionType;
  title: string;
  description: string;
  target: number;
  rewardGold: number;
  rewardBoostId?: string;
  rewardBoostDurationSec?: number;
}

export interface DailyProgress {
  dateKey: string;
  fuseCount: number;
  elementsCollected: number;
  newDiscoveries: number;
  goldEarned: number;
  claimedMissionIds: string[];
  bonusClaimed: boolean;
  streak: number;
  lastBonusDateKey: string | null;
}

export interface ActiveStorm {
  elementId: string;
  startedAt: number;
  expiresAt: number;
  nextAt: number;
}

export interface TapHarvestResult {
  success: boolean;
  elementId?: string;
  count?: number;
  goldGained?: number;
  error?: 'not_ready' | 'not_found';
}

export interface DailyBonusResult {
  success: boolean;
  goldGained?: number;
  streak?: number;
  error?: 'already_claimed';
}

export interface AdRewardProgress {
  dateKey: string;
  freeBoostUses: number;
  dailyGachaClaimed: boolean;
  goldFundingUses: number;
  fusionAssistUses: number;
}

export interface DailyGachaResult {
  success: boolean;
  elementId?: string;
  error?: 'already_claimed' | 'no_reward';
}

export const DAILY_MISSIONS: DailyMissionDefinition[] = [
  {
    id: 'daily_fuse_5',
    type: 'fuse',
    title: '연금술 점화',
    description: '합성을 5회 완료',
    target: 5,
    rewardGold: 120,
  },
  {
    id: 'daily_collect_60',
    type: 'collect',
    title: '작업장 순찰',
    description: '원소를 60개 수집',
    target: 60,
    rewardGold: 160,
    rewardBoostId: 'global_spawn_boost',
    rewardBoostDurationSec: 120,
  },
  {
    id: 'daily_discover_1',
    type: 'discover',
    title: '새 발견 기록',
    description: '새 원소 1종 발견',
    target: 1,
    rewardGold: 220,
  },
];

export interface TierAutomationGoal {
  tier: number;
  requiredTier: number;
  requiredCount: number;
  rewardGold: number;
  label: string;
}

export const TIER_AUTOMATION_GOALS: TierAutomationGoal[] = [
  {
    tier: 2,
    requiredTier: 2,
    requiredCount: 1,
    rewardGold: 50,
    label: 'Tier 2 자동 생산 해금',
  },
  {
    tier: 3,
    requiredTier: 2,
    requiredCount: 5,
    rewardGold: 180,
    label: 'Tier 3 자동 생산 해금',
  },
  {
    tier: 4,
    requiredTier: 3,
    requiredCount: 6,
    rewardGold: 600,
    label: 'Tier 4 자동 생산 해금',
  },
  {
    tier: 5,
    requiredTier: 4,
    requiredCount: 4,
    rewardGold: 1800,
    label: 'Tier 5 자동 생산 해금',
  },
];

function uniq(ids: string[]): string[] {
  return Array.from(new Set(ids));
}

function getDateKey(time = Date.now()): string {
  const date = new Date(time);
  const year = date.getFullYear();
  const month = String(date.getMonth() + 1).padStart(2, '0');
  const day = String(date.getDate()).padStart(2, '0');
  return `${year}-${month}-${day}`;
}

function getPreviousDateKey(dateKey: string): string {
  const [year, month, day] = dateKey.split('-').map(Number);
  const date = new Date(year, month - 1, day);
  date.setDate(date.getDate() - 1);
  return getDateKey(date.getTime());
}

function createDailyProgress(dateKey = getDateKey(), previous?: DailyProgress): DailyProgress {
  const streak =
    previous?.lastBonusDateKey === getPreviousDateKey(dateKey) ? previous.streak : 0;

  return {
    dateKey,
    fuseCount: 0,
    elementsCollected: 0,
    newDiscoveries: 0,
    goldEarned: 0,
    claimedMissionIds: [],
    bonusClaimed: previous?.lastBonusDateKey === dateKey && previous.bonusClaimed,
    streak,
    lastBonusDateKey: previous?.lastBonusDateKey ?? null,
  };
}

function ensureDailyProgress(progress: DailyProgress | undefined): DailyProgress {
  const today = getDateKey();
  if (!progress || progress.dateKey !== today) {
    return createDailyProgress(today, progress);
  }
  return progress;
}

function addDailyProgress(
  progress: DailyProgress,
  patch: Partial<Pick<DailyProgress, 'fuseCount' | 'elementsCollected' | 'newDiscoveries' | 'goldEarned'>>
): DailyProgress {
  const current = ensureDailyProgress(progress);
  return {
    ...current,
    fuseCount: current.fuseCount + (patch.fuseCount ?? 0),
    elementsCollected: current.elementsCollected + (patch.elementsCollected ?? 0),
    newDiscoveries: current.newDiscoveries + (patch.newDiscoveries ?? 0),
    goldEarned: current.goldEarned + (patch.goldEarned ?? 0),
  };
}

function createAdRewardProgress(dateKey = getDateKey()): AdRewardProgress {
  return {
    dateKey,
    freeBoostUses: 0,
    dailyGachaClaimed: false,
    goldFundingUses: 0,
    fusionAssistUses: 0,
  };
}

function ensureAdRewardProgress(progress: AdRewardProgress | undefined): AdRewardProgress {
  const today = getDateKey();
  if (!progress || progress.dateKey !== today) return createAdRewardProgress(today);
  return {
    dateKey: progress.dateKey,
    freeBoostUses: progress.freeBoostUses ?? 0,
    dailyGachaClaimed: progress.dailyGachaClaimed ?? false,
    goldFundingUses: progress.goldFundingUses ?? 0,
    fusionAssistUses: progress.fusionAssistUses ?? 0,
  };
}

export function getDailyMissionProgress(
  mission: DailyMissionDefinition,
  progress: DailyProgress
): number {
  switch (mission.type) {
    case 'fuse':
      return progress.fuseCount;
    case 'collect':
      return progress.elementsCollected;
    case 'discover':
      return progress.newDiscoveries;
  }
}

function pickStormElementId(discoveredElementIds: string[]): string {
  const candidates = elementsData.filter((el) => discoveredElementIds.includes(el.id));
  const pool = candidates.length > 0 ? candidates : elementsData.filter((el) => el.tier === 1);
  return pool[Math.floor(Math.random() * pool.length)]?.id ?? 'fire';
}

function pickDailyGachaElementId(
  elements: Record<string, number>,
  discoveredElements: string[]
): string | null {
  const discoveredElementIds = getDiscoveredElementIds(discoveredElements, elements);
  const craftableIds = recipesData
    .filter(
      (recipe) =>
        !discoveredElementIds.includes(recipe.result) &&
        isElementUnlocked(recipe.result, elements)
    )
    .map((recipe) => recipe.result);
  if (craftableIds.length > 0) {
    return craftableIds[Math.floor(Math.random() * craftableIds.length)];
  }

  const maxTier = Math.max(
    1,
    ...elementsData
      .filter((el) => discoveredElementIds.includes(el.id))
      .map((el) => el.tier)
  );
  const targetTier = Math.min(5, maxTier + 1);
  const nextTierIds = elementsData
    .filter((el) => el.tier === targetTier && !discoveredElementIds.includes(el.id))
    .map((el) => el.id);
  if (nextTierIds.length > 0) {
    return nextTierIds[Math.floor(Math.random() * nextTierIds.length)];
  }

  const fallbackIds = discoveredElementIds.filter((id) => (elements[id] ?? 0) > 0);
  if (fallbackIds.length === 0) return null;
  return fallbackIds[Math.floor(Math.random() * fallbackIds.length)];
}

function refreshStorm(
  storm: ActiveStorm | null,
  now: number,
  discoveredElementIds: string[]
): ActiveStorm {
  if (storm && storm.expiresAt > now) return storm;
  const nextAt = storm?.nextAt ?? now + STORM_INTERVAL_MS;
  if (now < nextAt) {
    return {
      elementId: storm?.elementId ?? pickStormElementId(discoveredElementIds),
      startedAt: storm?.startedAt ?? nextAt,
      expiresAt: storm?.expiresAt ?? nextAt,
      nextAt,
    };
  }

  return {
    elementId: pickStormElementId(discoveredElementIds),
    startedAt: now,
    expiresAt: now + STORM_DURATION_MS,
    nextAt: now + STORM_INTERVAL_MS,
  };
}

function getDiscoveredFromElements(elements: Record<string, number>): string[] {
  return Object.entries(elements)
    .filter(([, count]) => count > 0)
    .map(([id]) => id);
}

export function getDiscoveredElementIds(
  discoveredElements: string[] | undefined,
  elements: Record<string, number>
): string[] {
  return uniq([
    ...INITIAL_DISCOVERED_ELEMENT_IDS,
    ...(discoveredElements ?? []),
    ...getDiscoveredFromElements(elements),
  ]);
}

export function countDiscoveredByTier(discoveredElementIds: string[], tier: number): number {
  return elementsData.filter(
    (el) => el.tier === tier && discoveredElementIds.includes(el.id)
  ).length;
}

export function calcTierAutomationStatus(discoveredElementIds: string[]): {
  unlockedTier: number;
  nextGoal: TierAutomationGoal | null;
} {
  let unlockedTier = 1;

  for (const goal of TIER_AUTOMATION_GOALS) {
    if (countDiscoveredByTier(discoveredElementIds, goal.requiredTier) >= goal.requiredCount) {
      unlockedTier = Math.max(unlockedTier, goal.tier);
    }
  }

  return {
    unlockedTier,
    nextGoal: TIER_AUTOMATION_GOALS.find((goal) => goal.tier > unlockedTier) ?? null,
  };
}

function collectTierAutomationRewards(
  discoveredElementIds: string[],
  claimedTierRewards: number[]
): { newlyClaimedTiers: number[]; goldBonus: number } {
  const newlyClaimedTiers: number[] = [];
  let goldBonus = 0;

  for (const goal of TIER_AUTOMATION_GOALS) {
    if (claimedTierRewards.includes(goal.tier)) continue;
    if (countDiscoveredByTier(discoveredElementIds, goal.requiredTier) >= goal.requiredCount) {
      newlyClaimedTiers.push(goal.tier);
      goldBonus += goal.rewardGold;
    }
  }

  return { newlyClaimedTiers, goldBonus };
}

function calcComboMultiplier(comboCount: number): number {
  if (comboCount >= 10) return 3;
  if (comboCount >= 6) return 2;
  if (comboCount >= 3) return 1.5;
  return 1;
}

function pickRandomTier2ElementId(): string {
  const pool = elementsData.filter((el) => el.tier === 2);
  return pool[Math.floor(Math.random() * pool.length)]?.id ?? 'steam';
}

function pickLuckyResultId(baseResultId: string, discoveredElements: string[]): string {
  const baseTier =
    (elementsData as Array<{ id: string; tier: number }>).find((el) => el.id === baseResultId)
      ?.tier ?? 1;
  const targetTier = baseTier + 1;
  const candidates = elementsData.filter(
    (el) => el.tier === targetTier && !discoveredElements.includes(el.id)
  );
  if (candidates.length === 0) return baseResultId;
  return candidates[Math.floor(Math.random() * candidates.length)].id;
}

export function calcEnhanceCost(level: number): number {
  return Math.floor(ENHANCE_BASE_COST * Math.pow(ENHANCE_COST_MULTIPLIER, level));
}

export function calcEffectiveIdleRate(elementId: string, level: number): number {
  const base = elementsData.find((e) => e.id === elementId)?.idleIncomeRate ?? 1.0;
  return base * Math.pow(ENHANCE_RATE_MULTIPLIER, level);
}

export function calcSpawnRate(elementId: string, level: number): number {
  const el = elementsData.find((e) => e.id === elementId);
  const baseSpawnSpeed = el?.baseSpawnSpeed ?? 5.0;
  const tierMultiplier = TIER_SPAWN_RATE_MULTIPLIER[el?.tier ?? 1] ?? 1.0;
  return (1 / baseSpawnSpeed) * tierMultiplier * Math.pow(SPAWN_ENHANCE_MULTIPLIER, level);
}

export function calcResonanceBonuses(elements: Record<string, number>): {
  spawnMultiplier: number;
  goldMultiplier: number;
  power: number;
} {
  let power = 0;

  for (const el of elementsData) {
    const count = elements[el.id] ?? 0;
    if (count <= 0 || el.tier <= 1) continue;
    power += (el.tier - 1) * Math.sqrt(count);
  }

  return {
    spawnMultiplier: 1 + Math.min(MAX_RESONANCE_SPAWN_BONUS, power * 0.015),
    goldMultiplier: 1 + Math.min(MAX_RESONANCE_GOLD_BONUS, power * 0.02),
    power,
  };
}

export function isElementUnlocked(elementId: string, elements: Record<string, number>): boolean {
  const el = elementsData.find((e) => e.id === elementId);
  if (!el) return false;
  if (el.unlockCondition === 'initial') return true;
  if (el.unlockCondition.startsWith('recipe:')) {
    const ids = el.unlockCondition.replace('recipe:', '').split('+');
    const requiredCounts = ids.reduce<Record<string, number>>((acc, id) => {
      acc[id] = (acc[id] ?? 0) + 1;
      return acc;
    }, {});
    return Object.entries(requiredCounts).every(
      ([id, required]) => (elements[id] ?? 0) >= required
    );
  }
  return false;
}

export interface FuseResult {
  success: boolean;
  resultId?: string;
  goldGained?: number;
  comboCount?: number;
  comboMultiplier?: number;
  isLucky?: boolean;
  isNewDiscovery?: boolean;
  rarity?: string;
  error?: 'no_recipe' | 'insufficient_elements';
}

export interface EnhanceResult {
  success: boolean;
  newLevel?: number;
  cost?: number;
  error?: 'max_level' | 'insufficient_gold';
}

export interface OfflineReward {
  elements: Record<string, number>;
  gold: number;
  offlineSec: number;
}

export interface PrestigeResult {
  success: boolean;
  newPrestigeCount?: number;
  goldMultiplier?: number;
  spawnMultiplier?: number;
  title?: string;
  error?: 'not_enough_elements';
}

export interface AchievementStats {
  fuseCount: number;
  enhanceCount: number;
  offlineClaimCount: number;
  prestigeCount: number;
  tiersUnlocked: number[];
}

// 프레스티지 테이블에서 현재 횟수에 맞는 항목 조회
function getPrestigeEntry(count: number) {
  const table = prestigeTableData as Array<{
    count: number;
    goldMultiplier: number;
    spawnMultiplier: number;
    starterGold: number;
    title: string;
    titleEmoji: string;
    titleColor: string;
  }>;
  const entry = table.find((e) => e.count === count);
  // count가 테이블 최댓값을 초과하면 마지막 항목 사용
  if (!entry) return table[table.length - 1];
  return entry;
}

interface AchievementCondition {
  type: string;
  value: number;
  target?: string;
  tier?: number;
}

interface AchievementReward {
  gold?: number;
  boostType?: string;
  boostDurationMin?: number;
  bonusScrolls?: number;
  permanentGoldMultiplier?: number;
  permanentSpawnMultiplier?: number;
  title?: string;
  specialBackground?: string;
}

interface Achievement {
  id: string;
  name: string;
  description: string;
  category: string;
  condition: AchievementCondition;
  reward: AchievementReward;
  isHidden: boolean;
}

const ACHIEVEMENTS = achievementsData as Achievement[];

function checkAchievementCondition(
  condition: AchievementCondition,
  state: {
    elements: Record<string, number>;
    gold: number;
    elementLevels: Record<string, number>;
    achievementStats: AchievementStats;
  }
): boolean {
  const { elements, gold, elementLevels, achievementStats } = state;

  switch (condition.type) {
    case 'fuse_count':
      return achievementStats.fuseCount >= condition.value;

    case 'element_count':
      return (elements[condition.target ?? ''] ?? 0) >= condition.value;

    case 'tier_unlock':
      return achievementStats.tiersUnlocked.includes(condition.value);

    case 'enhance_count':
      return achievementStats.enhanceCount >= condition.value;

    case 'offline_claim':
      return achievementStats.offlineClaimCount >= condition.value;

    case 'element_variety':
      return Object.values(elements).filter((c) => c > 0).length >= condition.value;

    case 'gold_amount':
      return gold >= condition.value;

    case 'max_enhance_count': {
      const maxLevelCount = Object.values(elementLevels).filter(
        (lv) => lv >= ENHANCE_MAX_LEVEL
      ).length;
      return maxLevelCount >= condition.value;
    }

    case 'total_elements':
      return Object.values(elements).reduce((sum, c) => sum + c, 0) >= condition.value;

    case 'single_element_count':
      return Math.max(0, ...Object.values(elements)) >= condition.value;

    case 'tier_variety': {
      const targetTier = condition.tier ?? 0;
      const tierElementIds = (elementsData as Array<{ id: string; tier: number }>)
        .filter((e) => e.tier === targetTier)
        .map((e) => e.id);
      const discoveredCount = tierElementIds.filter((id) => (elements[id] ?? 0) > 0).length;
      return discoveredCount >= condition.value;
    }

    case 'all_enhance_min_level': {
      const discoveredIds = Object.entries(elements)
        .filter(([, count]) => count > 0)
        .map(([id]) => id);
      if (discoveredIds.length === 0) return false;
      return discoveredIds.every((id) => (elementLevels[id] ?? 0) >= condition.value);
    }

    case 'element_unlock':
      return (elements[condition.target ?? ''] ?? 0) > 0;

    case 'prestige_count':
      return achievementStats.prestigeCount >= condition.value;

    default:
      return false;
  }
}

interface AchievementUnlockResult {
  newlyUnlocked: Achievement[];
  goldBonus: number;
  permanentGoldMultiplierBonus: number;
  permanentSpawnMultiplierBonus: number;
}

function collectNewAchievements(
  state: {
    elements: Record<string, number>;
    gold: number;
    elementLevels: Record<string, number>;
    achievementStats: AchievementStats;
    unlockedAchievements: string[];
  }
): AchievementUnlockResult {
  const newlyUnlocked: Achievement[] = [];
  let goldBonus = 0;
  let permanentGoldMultiplierBonus = 0;
  let permanentSpawnMultiplierBonus = 0;

  for (const ach of ACHIEVEMENTS) {
    if (state.unlockedAchievements.includes(ach.id)) continue;
    if (checkAchievementCondition(ach.condition, state)) {
      newlyUnlocked.push(ach);
      goldBonus += ach.reward.gold ?? 0;
      permanentGoldMultiplierBonus += ach.reward.permanentGoldMultiplier ?? 0;
      permanentSpawnMultiplierBonus += ach.reward.permanentSpawnMultiplier ?? 0;
    }
  }

  return { newlyUnlocked, goldBonus, permanentGoldMultiplierBonus, permanentSpawnMultiplierBonus };
}

interface GameState {
  activeTab: TabName;
  setActiveTab: (tab: TabName) => void;

  // 원소 인벤토리
  elements: Record<string, number>;
  discoveredElements: string[];
  gold: number;
  addGold: (amount: number) => void;
  addElement: (elementId: string, count?: number) => void;

  // 원소 강화 레벨 (elementId -> level 0~5)
  elementLevels: Record<string, number>;
  enhance: (elementId: string) => EnhanceResult;

  // 합성 액션
  comboCount: number;
  comboExpiresAt: number;
  fuse: (slot1Id: string, slot2Id: string) => FuseResult;

  // 방치형 시스템
  lastTickAt: number;
  spawnAccumulators: Record<string, number>;
  pendingOfflineReward: OfflineReward | null;
  tickIdle: (deltaSec: number) => void;
  initOffline: () => void;
  claimOfflineReward: (multiplier?: number) => void;
  harvestElement: (elementId: string) => TapHarvestResult;

  // 상점 버프 시스템 (boostId -> 만료 시각 ms)
  activeBoosts: Record<string, number>;
  activateBoost: (boostId: string, durationSec: number) => void;
  claimedTierRewards: number[];

  // v1.1 리텐션 시스템
  dailyProgress: DailyProgress;
  activeStorm: ActiveStorm | null;
  adRewardProgress: AdRewardProgress;
  claimDailyBonus: () => DailyBonusResult;
  claimDailyMission: (missionId: string) => boolean;
  claimAdFreeBoost: () => boolean;
  claimAdDailyGacha: () => DailyGachaResult;
  claimAdGoldFunding: (amount: number) => boolean;
  claimAdFusionAssist: () => boolean;

  // 프레스티지 시스템
  prestigeCount: number;
  prestigeGoldMultiplier: number;
  prestigeSpawnMultiplier: number;
  prestigeTitle: string;
  prestige: () => PrestigeResult;

  // 업적 시스템
  unlockedAchievements: string[];
  permanentGoldMultiplier: number;
  permanentSpawnMultiplier: number;
  achievementStats: AchievementStats;

  // 튜토리얼 상태
  tutorialStep: number;       // 0~5 (0=미시작, 5=완료)
  tutorialCompleted: boolean; // true면 튜토리얼 UI 완전히 숨김
  welcomeGiftClaimed: boolean;
  advanceTutorial: () => void;
  skipTutorial: () => void;

  // 설정
  language: Language;
  bgmEnabled: boolean;
  sfxEnabled: boolean;
  hapticEnabled: boolean;
  setLanguage: (lang: Language) => void;
  setBgmEnabled: (enabled: boolean) => void;
  setSfxEnabled: (enabled: boolean) => void;
  setHapticEnabled: (enabled: boolean) => void;
  resetGame: () => void;
}

type PersistedState = Pick<
  GameState,
  | 'activeTab'
  | 'elements'
  | 'discoveredElements'
  | 'gold'
  | 'elementLevels'
  | 'comboCount'
  | 'comboExpiresAt'
  | 'lastTickAt'
  | 'activeBoosts'
  | 'claimedTierRewards'
  | 'dailyProgress'
  | 'activeStorm'
  | 'adRewardProgress'
  | 'spawnAccumulators'
  | 'language'
  | 'bgmEnabled'
  | 'prestigeCount'
  | 'prestigeGoldMultiplier'
  | 'prestigeSpawnMultiplier'
  | 'prestigeTitle'
  | 'unlockedAchievements'
  | 'permanentGoldMultiplier'
  | 'permanentSpawnMultiplier'
  | 'achievementStats'
  | 'tutorialStep'
  | 'tutorialCompleted'
  | 'welcomeGiftClaimed'
  | 'sfxEnabled'
  | 'hapticEnabled'
>;

export const useGameStore = create<GameState>()(
  persist(
    (set, get) => ({
      activeTab: 'elements',
      setActiveTab: (tab) => set({ activeTab: tab }),

      elements: { ...INITIAL_ELEMENTS },
      discoveredElements: [...INITIAL_DISCOVERED_ELEMENT_IDS],
      gold: 0,
      activeBoosts: {},
      claimedTierRewards: [],
      dailyProgress: createDailyProgress(),
      activeStorm: null,
      adRewardProgress: createAdRewardProgress(),
      comboCount: 0,
      comboExpiresAt: 0,

      addGold: (amount) =>
        set((state) => ({
          gold: state.gold + amount,
          dailyProgress:
            amount > 0
              ? addDailyProgress(state.dailyProgress, { goldEarned: amount })
              : ensureDailyProgress(state.dailyProgress),
        })),

      addElement: (elementId, count = 1) =>
        set((state) => {
          const elements = {
            ...state.elements,
            [elementId]: (state.elements[elementId] ?? 0) + count,
          };
          const wasDiscovered = getDiscoveredElementIds(
            state.discoveredElements,
            state.elements
          ).includes(elementId);
          const discoveredElements = getDiscoveredElementIds(
            [...state.discoveredElements, elementId],
            elements
          );
          const { newlyClaimedTiers, goldBonus } = collectTierAutomationRewards(
            discoveredElements,
            state.claimedTierRewards
          );

          return {
            elements,
            discoveredElements,
            gold: state.gold + goldBonus,
            dailyProgress: addDailyProgress(state.dailyProgress, {
              elementsCollected: Math.max(0, count),
              newDiscoveries: wasDiscovered ? 0 : 1,
              goldEarned: goldBonus,
            }),
            claimedTierRewards: [...state.claimedTierRewards, ...newlyClaimedTiers],
          };
        }),

      elementLevels: {},

      enhance: (elementId) => {
        const { elementLevels, gold } = get();
        const currentLevel = elementLevels[elementId] ?? 0;

        if (currentLevel >= ENHANCE_MAX_LEVEL) {
          return { success: false, error: 'max_level' };
        }

        const cost = calcEnhanceCost(currentLevel);
        if (gold < cost) {
          return { success: false, error: 'insufficient_gold' };
        }

        const newLevel = currentLevel + 1;
        set((state) => {
          const newElementLevels = {
            ...state.elementLevels,
            [elementId]: newLevel,
          };
          const newStats: AchievementStats = {
            ...state.achievementStats,
            enhanceCount: state.achievementStats.enhanceCount + 1,
          };
          const checkState = {
            elements: state.elements,
            gold: state.gold - cost,
            elementLevels: newElementLevels,
            achievementStats: newStats,
            unlockedAchievements: state.unlockedAchievements,
          };
          const { newlyUnlocked, goldBonus, permanentGoldMultiplierBonus, permanentSpawnMultiplierBonus } =
            collectNewAchievements(checkState);
          for (const ach of newlyUnlocked) {
            window.dispatchEvent(new CustomEvent('newAchievement', { detail: ach }));
          }
          return {
            gold: state.gold - cost + goldBonus,
            elementLevels: newElementLevels,
            achievementStats: newStats,
            unlockedAchievements: [
              ...state.unlockedAchievements,
              ...newlyUnlocked.map((a) => a.id),
            ],
            permanentGoldMultiplier: state.permanentGoldMultiplier + permanentGoldMultiplierBonus,
            permanentSpawnMultiplier:
              state.permanentSpawnMultiplier + permanentSpawnMultiplierBonus,
          };
        });

        return { success: true, newLevel, cost };
      },

      fuse: (slot1Id, slot2Id) => {
        const { elements, discoveredElements, comboCount, comboExpiresAt } = get();

        const recipe = recipesData.find(
          (r) =>
            (r.ingredients[0] === slot1Id && r.ingredients[1] === slot2Id) ||
            (r.ingredients[0] === slot2Id && r.ingredients[1] === slot1Id)
        );

        if (!recipe) return { success: false, error: 'no_recipe' };

        const slot1Count = elements[slot1Id] ?? 0;
        const slot2Count = elements[slot2Id] ?? 0;
        const sameElement = slot1Id === slot2Id;

        if (sameElement && slot1Count < 2) return { success: false, error: 'insufficient_elements' };
        if (!sameElement && (slot1Count < 1 || slot2Count < 1))
          return { success: false, error: 'insufficient_elements' };

        const now = Date.now();
        const nextComboCount = comboExpiresAt > now ? comboCount + 1 : 1;
        const comboMultiplier = calcComboMultiplier(nextComboCount);
        const isLucky = Math.random() < LUCKY_FUSION_RATE;
        const actualResultId = isLucky
          ? pickLuckyResultId(recipe.result, getDiscoveredElementIds(discoveredElements, elements))
          : recipe.result;
        const actualResult = elementsData.find((el) => el.id === actualResultId);
        const goldGained = Math.floor(recipe.tier * 10 * comboMultiplier);
        const resultTier =
          (elementsData as Array<{ id: string; tier: number }>).find(
            (e) => e.id === actualResultId
          )?.tier ?? 0;
        const wasDiscovered = getDiscoveredElementIds(discoveredElements, elements).includes(
          actualResultId
        );

        set((state) => {
          const next = { ...state.elements };
          next[slot1Id] = (next[slot1Id] ?? 0) - 1;
          if (!sameElement) {
            next[slot2Id] = (next[slot2Id] ?? 0) - 1;
          }
          next[actualResultId] = (next[actualResultId] ?? 0) + 1;
          const discoveredElements = getDiscoveredElementIds(
            [...state.discoveredElements, actualResultId],
            next
          );
          const { newlyClaimedTiers, goldBonus: tierRewardGold } =
            collectTierAutomationRewards(discoveredElements, state.claimedTierRewards);

          const newTiersUnlocked = state.achievementStats.tiersUnlocked.includes(resultTier)
            ? state.achievementStats.tiersUnlocked
            : [...state.achievementStats.tiersUnlocked, resultTier];

          const newStats: AchievementStats = {
            ...state.achievementStats,
            fuseCount: state.achievementStats.fuseCount + 1,
            tiersUnlocked: newTiersUnlocked,
          };
          const nextGold = state.gold + goldGained + tierRewardGold;

          const checkState = {
            elements: next,
            gold: nextGold,
            elementLevels: state.elementLevels,
            achievementStats: newStats,
            unlockedAchievements: state.unlockedAchievements,
          };
          const { newlyUnlocked, goldBonus, permanentGoldMultiplierBonus, permanentSpawnMultiplierBonus } =
            collectNewAchievements(checkState);
          for (const ach of newlyUnlocked) {
            window.dispatchEvent(new CustomEvent('newAchievement', { detail: ach }));
          }

          return {
            elements: next,
            discoveredElements,
            gold: nextGold + goldBonus,
            dailyProgress: addDailyProgress(state.dailyProgress, {
              fuseCount: 1,
              elementsCollected: 1,
              newDiscoveries: wasDiscovered ? 0 : 1,
              goldEarned: goldGained + tierRewardGold + goldBonus,
            }),
            claimedTierRewards: [...state.claimedTierRewards, ...newlyClaimedTiers],
            comboCount: nextComboCount,
            comboExpiresAt: now + COMBO_WINDOW_MS,
            achievementStats: newStats,
            unlockedAchievements: [
              ...state.unlockedAchievements,
              ...newlyUnlocked.map((a) => a.id),
            ],
            permanentGoldMultiplier: state.permanentGoldMultiplier + permanentGoldMultiplierBonus,
            permanentSpawnMultiplier:
              state.permanentSpawnMultiplier + permanentSpawnMultiplierBonus,
          };
        });

        return {
          success: true,
          resultId: actualResultId,
          goldGained,
          comboCount: nextComboCount,
          comboMultiplier,
          isLucky: actualResultId !== recipe.result,
          isNewDiscovery: !wasDiscovered,
          rarity: actualResult?.rarity,
        };
      },

      // 설정
      language: 'ko',
      bgmEnabled: false,
      sfxEnabled: true,
      hapticEnabled: true,
      setLanguage: (lang) => set({ language: lang }),
      setBgmEnabled: (enabled) => set({ bgmEnabled: enabled }),
      setSfxEnabled: (enabled) => set({ sfxEnabled: enabled }),
      setHapticEnabled: (enabled) => set({ hapticEnabled: enabled }),
      resetGame: () => {
        flushGameState();
        localStorage.removeItem('archetype-game-state');
        window.location.reload();
      },

      // 상점 버프 시스템
      activateBoost: (boostId, durationSec) => {
        set((state) => ({
          activeBoosts: {
            ...state.activeBoosts,
            [boostId]: Date.now() + durationSec * 1000,
          },
        }));
      },

      claimDailyBonus: () => {
        const progress = ensureDailyProgress(get().dailyProgress);
        if (progress.bonusClaimed) return { success: false, error: 'already_claimed' };

        const nextStreak = progress.streak + 1;
        const goldGained = 100 + Math.min(6, nextStreak - 1) * 25;
        set((state) => {
          const current = ensureDailyProgress(state.dailyProgress);
          return {
            gold: state.gold + goldGained,
            dailyProgress: {
              ...current,
              bonusClaimed: true,
              streak: nextStreak,
              lastBonusDateKey: current.dateKey,
              goldEarned: current.goldEarned + goldGained,
            },
          };
        });

        return { success: true, goldGained, streak: nextStreak };
      },

      claimDailyMission: (missionId) => {
        const mission = DAILY_MISSIONS.find((item) => item.id === missionId);
        if (!mission) return false;

        const progress = ensureDailyProgress(get().dailyProgress);
        if (progress.claimedMissionIds.includes(missionId)) return false;
        if (getDailyMissionProgress(mission, progress) < mission.target) return false;

        set((state) => {
          const current = ensureDailyProgress(state.dailyProgress);
          const activeBoosts = { ...state.activeBoosts };
          if (mission.rewardBoostId && mission.rewardBoostDurationSec) {
            activeBoosts[mission.rewardBoostId] =
              Date.now() + mission.rewardBoostDurationSec * 1000;
          }

          return {
            gold: state.gold + mission.rewardGold,
            activeBoosts,
            dailyProgress: {
              ...current,
              goldEarned: current.goldEarned + mission.rewardGold,
              claimedMissionIds: [...current.claimedMissionIds, missionId],
            },
          };
        });

        return true;
      },

      claimAdFreeBoost: () => {
        const progress = ensureAdRewardProgress(get().adRewardProgress);
        if (progress.freeBoostUses >= 3) return false;

        set((state) => {
          const current = ensureAdRewardProgress(state.adRewardProgress);
          if (current.freeBoostUses >= 3) return {};

          const now = Date.now();
          const currentExpiresAt = state.activeBoosts.global_spawn_boost ?? 0;
          const baseTime = Math.max(now, currentExpiresAt);

          return {
            activeBoosts: {
              ...state.activeBoosts,
              global_spawn_boost: baseTime + 30 * 60 * 1000,
            },
            adRewardProgress: {
              ...current,
              freeBoostUses: current.freeBoostUses + 1,
            },
          };
        });

        return true;
      },

      claimAdDailyGacha: () => {
        const progress = ensureAdRewardProgress(get().adRewardProgress);
        if (progress.dailyGachaClaimed) return { success: false, error: 'already_claimed' };

        let result: DailyGachaResult = { success: false, error: 'no_reward' };
        set((state) => {
          const current = ensureAdRewardProgress(state.adRewardProgress);
          if (current.dailyGachaClaimed) {
            result = { success: false, error: 'already_claimed' };
            return {};
          }

          const elementId = pickDailyGachaElementId(state.elements, state.discoveredElements);
          if (!elementId) {
            result = { success: false, error: 'no_reward' };
            return {};
          }

          const wasDiscovered = getDiscoveredElementIds(
            state.discoveredElements,
            state.elements
          ).includes(elementId);
          const elements = {
            ...state.elements,
            [elementId]: (state.elements[elementId] ?? 0) + 1,
          };
          const discoveredElements = getDiscoveredElementIds(
            [...state.discoveredElements, elementId],
            elements
          );
          const { newlyClaimedTiers, goldBonus } = collectTierAutomationRewards(
            discoveredElements,
            state.claimedTierRewards
          );

          result = { success: true, elementId };

          return {
            elements,
            discoveredElements,
            gold: state.gold + goldBonus,
            claimedTierRewards: [...state.claimedTierRewards, ...newlyClaimedTiers],
            dailyProgress: addDailyProgress(state.dailyProgress, {
              elementsCollected: 1,
              newDiscoveries: wasDiscovered ? 0 : 1,
              goldEarned: goldBonus,
            }),
            adRewardProgress: {
              ...current,
              dailyGachaClaimed: true,
            },
          };
        });

        return result;
      },

      claimAdGoldFunding: (amount) => {
        const goldGained = Math.max(0, Math.ceil(amount));
        if (goldGained <= 0) return false;
        const progress = ensureAdRewardProgress(get().adRewardProgress);
        if (progress.goldFundingUses >= 5) return false;

        set((state) => {
          const current = ensureAdRewardProgress(state.adRewardProgress);
          if (current.goldFundingUses >= 5) return {};

          return {
            gold: state.gold + goldGained,
            dailyProgress: addDailyProgress(state.dailyProgress, {
              goldEarned: goldGained,
            }),
            adRewardProgress: {
              ...current,
              goldFundingUses: current.goldFundingUses + 1,
            },
          };
        });

        return true;
      },

      claimAdFusionAssist: () => {
        const progress = ensureAdRewardProgress(get().adRewardProgress);
        if (progress.fusionAssistUses >= 5) return false;

        set((state) => {
          const current = ensureAdRewardProgress(state.adRewardProgress);
          if (current.fusionAssistUses >= 5) return {};

          return {
            adRewardProgress: {
              ...current,
              fusionAssistUses: current.fusionAssistUses + 1,
            },
          };
        });

        return true;
      },

      // 프레스티지 시스템
      prestigeCount: 0,
      prestigeGoldMultiplier: 1.0,
      prestigeSpawnMultiplier: 1.0,
      prestigeTitle: '',

      prestige: () => {
        const state = get();
        const creationCount = state.elements['creation'] ?? 0;
        const spiritCount = state.elements['spirit'] ?? 0;

        if (creationCount < 1 || spiritCount < 1) {
          return { success: false, error: 'not_enough_elements' };
        }

        const newPrestigeCount = state.prestigeCount + 1;
        const entry = getPrestigeEntry(newPrestigeCount);

        const newStats: AchievementStats = {
          ...state.achievementStats,
          prestigeCount: newPrestigeCount,
        };

        const checkState = {
          elements: { ...INITIAL_ELEMENTS },
          gold: entry.starterGold,
          elementLevels: {},
          achievementStats: newStats,
          unlockedAchievements: state.unlockedAchievements,
        };
        const { newlyUnlocked, goldBonus, permanentGoldMultiplierBonus, permanentSpawnMultiplierBonus } =
          collectNewAchievements(checkState);
        for (const ach of newlyUnlocked) {
          window.dispatchEvent(new CustomEvent('newAchievement', { detail: ach }));
        }

        set({
          // 초기화 항목
          elements: { ...INITIAL_ELEMENTS },
          elementLevels: {},
          activeBoosts: {},
          // 골드는 스타터 골드 + 업적 보너스
          gold: entry.starterGold + goldBonus,
          // 유지/업데이트 항목
          prestigeCount: newPrestigeCount,
          prestigeGoldMultiplier: entry.goldMultiplier,
          prestigeSpawnMultiplier: entry.spawnMultiplier,
          prestigeTitle: `${entry.titleEmoji} ${entry.title}`,
          achievementStats: newStats,
          unlockedAchievements: [
            ...state.unlockedAchievements,
            ...newlyUnlocked.map((a) => a.id),
          ],
          permanentGoldMultiplier:
            state.permanentGoldMultiplier + permanentGoldMultiplierBonus,
          permanentSpawnMultiplier:
            state.permanentSpawnMultiplier + permanentSpawnMultiplierBonus,
          spawnAccumulators: {},
          lastTickAt: Date.now(),
        });

        return {
          success: true,
          newPrestigeCount,
          goldMultiplier: entry.goldMultiplier,
          spawnMultiplier: entry.spawnMultiplier,
          title: `${entry.titleEmoji} ${entry.title}`,
        };
      },

      // 업적 시스템
      unlockedAchievements: [],
      permanentGoldMultiplier: 1.0,
      permanentSpawnMultiplier: 1.0,
      achievementStats: {
        fuseCount: 0,
        enhanceCount: 0,
        offlineClaimCount: 0,
        prestigeCount: 0,
        tiersUnlocked: [],
      },

      // 튜토리얼 상태
      tutorialStep: 0,
      tutorialCompleted: false,
      welcomeGiftClaimed: false,
      advanceTutorial: () =>
        set((state) => {
          const nextStep = state.tutorialStep + 1;
          if (nextStep >= 5 && !state.welcomeGiftClaimed) {
            const giftId = pickRandomTier2ElementId();
            const elements = {
              ...state.elements,
              [giftId]: (state.elements[giftId] ?? 0) + 1,
            };
            const discoveredElements = getDiscoveredElementIds(
              [...state.discoveredElements, giftId],
              elements
            );
            const { newlyClaimedTiers, goldBonus } = collectTierAutomationRewards(
              discoveredElements,
              state.claimedTierRewards
            );
            return {
              elements,
              discoveredElements,
              gold: state.gold + goldBonus + 120,
              claimedTierRewards: [...state.claimedTierRewards, ...newlyClaimedTiers],
              tutorialStep: nextStep,
              tutorialCompleted: true,
              welcomeGiftClaimed: true,
            };
          }
          return {
            tutorialStep: nextStep,
            tutorialCompleted: nextStep >= 5,
          };
        }),
      skipTutorial: () =>
        set((state) => ({
          tutorialStep: 5,
          tutorialCompleted: true,
          welcomeGiftClaimed: state.welcomeGiftClaimed,
        })),

      // 방치형 시스템
      lastTickAt: Date.now(),
      spawnAccumulators: {},
      pendingOfflineReward: null,

      tickIdle: (deltaSec) => {
        const {
          elements,
          discoveredElements,
          elementLevels,
          spawnAccumulators,
          activeBoosts,
          prestigeGoldMultiplier,
          prestigeSpawnMultiplier,
          permanentGoldMultiplier,
          permanentSpawnMultiplier,
          tutorialCompleted,
          tutorialStep,
          activeStorm,
        } = get();
        if (!tutorialCompleted && tutorialStep <= 2) {
          set({ lastTickAt: Date.now() });
          return;
        }
        const newAccumulators = { ...spawnAccumulators };
        const spawnedElements: Record<string, number> = {};
        let goldGained = 0;
        const now = Date.now();
        const discoveredElementIds = getDiscoveredElementIds(discoveredElements, elements);
        const automationStatus = calcTierAutomationStatus(discoveredElementIds);
        const resonance = calcResonanceBonuses(elements);
        const currentStorm = refreshStorm(activeStorm, now, discoveredElementIds);
        const globalSpawnMultiplier = (activeBoosts.global_spawn_boost ?? 0) > now ? 2.0 : 1.0;
        const goldBoostMultiplier = (activeBoosts.gold_boost ?? 0) > now ? 2.0 : 1.0;

        for (const el of elementsData) {
          if (!discoveredElementIds.includes(el.id) || el.tier > automationStatus.unlockedTier) {
            continue;
          }
          const level = elementLevels[el.id] ?? 0;

          // 버프 배수 계산 (해당 원소 강화석이 활성 중이면 2배)
          const boostId = el.id + '_boost';
          const boostMultiplier = (activeBoosts[boostId] ?? 0) > now ? 2.0 : 1.0;
          const tierBoostMultiplier = (activeBoosts[`tier_${el.tier}_boost`] ?? 0) > now ? 2.0 : 1.0;
          const stormMultiplier =
            currentStorm.expiresAt > now && currentStorm.elementId === el.id ? 5.0 : 1.0;

          // 원소 자동 생성 (프레스티지 + 영구 스폰 배율 적용)
          const rate =
            calcSpawnRate(el.id, level) *
            boostMultiplier *
            tierBoostMultiplier *
            stormMultiplier *
            globalSpawnMultiplier *
            prestigeSpawnMultiplier *
            permanentSpawnMultiplier *
            resonance.spawnMultiplier;
          const prev = newAccumulators[el.id] ?? 0;
          const next = prev + rate * deltaSec;
          const spawned = Math.floor(next);
          newAccumulators[el.id] = next - spawned;
          if (spawned > 0) spawnedElements[el.id] = spawned;

          // 골드 자동 수입 (프레스티지 + 영구 골드 배율 적용)
          goldGained +=
            calcEffectiveIdleRate(el.id, level) *
            deltaSec *
            goldBoostMultiplier *
            prestigeGoldMultiplier *
            permanentGoldMultiplier *
            resonance.goldMultiplier;
        }

        set((state) => {
          const newElements = { ...state.elements };
          for (const [id, count] of Object.entries(spawnedElements)) {
            newElements[id] = (newElements[id] ?? 0) + count;
          }
          const newDiscoveredElements = getDiscoveredElementIds(
            state.discoveredElements,
            newElements
          );
          const goldToAdd = Math.floor(goldGained);
          const collectedCount = Object.values(spawnedElements).reduce((sum, count) => sum + count, 0);

          // tickIdle에서도 gold_amount, element_count 등 업적 체크
          const checkState = {
            elements: newElements,
            gold: state.gold + goldToAdd,
            elementLevels: state.elementLevels,
            achievementStats: state.achievementStats,
            unlockedAchievements: state.unlockedAchievements,
          };
          const {
            newlyUnlocked,
            goldBonus,
            permanentGoldMultiplierBonus,
            permanentSpawnMultiplierBonus,
          } = collectNewAchievements(checkState);
          for (const ach of newlyUnlocked) {
            window.dispatchEvent(new CustomEvent('newAchievement', { detail: ach }));
          }

          return {
            elements: newElements,
            discoveredElements: newDiscoveredElements,
            gold: state.gold + goldToAdd + goldBonus,
            spawnAccumulators: newAccumulators,
            lastTickAt: Date.now(),
            activeStorm: currentStorm,
            dailyProgress: addDailyProgress(state.dailyProgress, {
              elementsCollected: collectedCount,
              goldEarned: goldToAdd + goldBonus,
            }),
            unlockedAchievements: [
              ...state.unlockedAchievements,
              ...newlyUnlocked.map((a) => a.id),
            ],
            permanentGoldMultiplier:
              state.permanentGoldMultiplier + permanentGoldMultiplierBonus,
            permanentSpawnMultiplier:
              state.permanentSpawnMultiplier + permanentSpawnMultiplierBonus,
          };
        });
      },

      initOffline: () => {
        const {
          lastTickAt,
          elements,
          discoveredElements,
          elementLevels,
          prestigeSpawnMultiplier,
          permanentSpawnMultiplier,
          prestigeGoldMultiplier,
          permanentGoldMultiplier,
        } = get();
        const now = Date.now();
        const offlineSec = Math.min((now - lastTickAt) / 1000, MAX_OFFLINE_SECONDS);

        if (offlineSec < 10) {
          set({ lastTickAt: now });
          return;
        }

        const rewardElements: Record<string, number> = {};
        let rewardGold = 0;
        const discoveredElementIds = getDiscoveredElementIds(discoveredElements, elements);
        const automationStatus = calcTierAutomationStatus(discoveredElementIds);
        const resonance = calcResonanceBonuses(elements);
        const activeBoosts = get().activeBoosts;
        const nowForBoosts = Date.now();
        const globalSpawnMultiplier =
          (activeBoosts.global_spawn_boost ?? 0) > nowForBoosts ? 2.0 : 1.0;
        const goldBoostMultiplier = (activeBoosts.gold_boost ?? 0) > nowForBoosts ? 2.0 : 1.0;

        for (const el of elementsData) {
          if (!discoveredElementIds.includes(el.id) || el.tier > automationStatus.unlockedTier) {
            continue;
          }
          const level = elementLevels[el.id] ?? 0;
          const tierBoostMultiplier =
            (activeBoosts[`tier_${el.tier}_boost`] ?? 0) > nowForBoosts ? 2.0 : 1.0;

          const spawned = Math.floor(
            calcSpawnRate(el.id, level) *
              tierBoostMultiplier *
              globalSpawnMultiplier *
              prestigeSpawnMultiplier *
              permanentSpawnMultiplier *
              resonance.spawnMultiplier *
              offlineSec
          );
          if (spawned > 0) rewardElements[el.id] = spawned;

          rewardGold +=
            calcEffectiveIdleRate(el.id, level) *
            offlineSec *
            goldBoostMultiplier *
            prestigeGoldMultiplier *
            permanentGoldMultiplier *
            resonance.goldMultiplier;
        }

        set({
          lastTickAt: now,
          pendingOfflineReward: {
            elements: rewardElements,
            gold: Math.floor(rewardGold),
            offlineSec: Math.floor(offlineSec),
          },
        });
      },

      claimOfflineReward: (multiplier = 1) => {
        const safeMultiplier = Math.max(1, Math.floor(multiplier));

        set((state) => {
          const pendingOfflineReward = state.pendingOfflineReward;
          if (!pendingOfflineReward) return {};

          const newElements = { ...state.elements };
          for (const [id, count] of Object.entries(pendingOfflineReward.elements)) {
            newElements[id] = (newElements[id] ?? 0) + count * safeMultiplier;
          }
          const rewardGold = pendingOfflineReward.gold * safeMultiplier;
          const newDiscoveredElements = getDiscoveredElementIds(
            state.discoveredElements,
            newElements
          );
          const newStats: AchievementStats = {
            ...state.achievementStats,
            offlineClaimCount: state.achievementStats.offlineClaimCount + 1,
          };
          const checkState = {
            elements: newElements,
            gold: state.gold + rewardGold,
            elementLevels: state.elementLevels,
            achievementStats: newStats,
            unlockedAchievements: state.unlockedAchievements,
          };
          const {
            newlyUnlocked,
            goldBonus,
            permanentGoldMultiplierBonus,
            permanentSpawnMultiplierBonus,
          } = collectNewAchievements(checkState);
          for (const ach of newlyUnlocked) {
            window.dispatchEvent(new CustomEvent('newAchievement', { detail: ach }));
          }
          const collectedCount = Object.values(pendingOfflineReward.elements).reduce(
            (sum, count) => sum + count * safeMultiplier,
            0
          );

          return {
            elements: newElements,
            discoveredElements: newDiscoveredElements,
            gold: state.gold + rewardGold + goldBonus,
            pendingOfflineReward: null,
            achievementStats: newStats,
            dailyProgress: addDailyProgress(state.dailyProgress, {
              elementsCollected: collectedCount,
              goldEarned: rewardGold + goldBonus,
            }),
            unlockedAchievements: [
              ...state.unlockedAchievements,
              ...newlyUnlocked.map((a) => a.id),
            ],
            permanentGoldMultiplier:
              state.permanentGoldMultiplier + permanentGoldMultiplierBonus,
            permanentSpawnMultiplier:
              state.permanentSpawnMultiplier + permanentSpawnMultiplierBonus,
          };
        });
      },

      harvestElement: (elementId) => {
        let result: TapHarvestResult = { success: false, error: 'not_ready' };

        set((state) => {
          const el = elementsData.find((item) => item.id === elementId);
          if (!el) {
            result = { success: false, error: 'not_found' };
            return {};
          }

          const discoveredElementIds = getDiscoveredElementIds(
            state.discoveredElements,
            state.elements
          );
          const automationStatus = calcTierAutomationStatus(discoveredElementIds);
          const progress = state.spawnAccumulators[elementId] ?? 0;
          if (
            !discoveredElementIds.includes(elementId) ||
            el.tier > automationStatus.unlockedTier ||
            progress < TAP_HARVEST_THRESHOLD
          ) {
            result = { success: false, error: 'not_ready' };
            return {};
          }

          const now = Date.now();
          const level = state.elementLevels[elementId] ?? 0;
          const goldBoostMultiplier = (state.activeBoosts.gold_boost ?? 0) > now ? 2.0 : 1.0;
          const resonance = calcResonanceBonuses(state.elements);
          const elementGain = Math.ceil(TAP_HARVEST_BONUS);
          const goldGained = Math.ceil(
            calcEffectiveIdleRate(elementId, level) *
              5 *
              TAP_HARVEST_BONUS *
              goldBoostMultiplier *
              state.prestigeGoldMultiplier *
              state.permanentGoldMultiplier *
              resonance.goldMultiplier
          );
          const elements = {
            ...state.elements,
            [elementId]: (state.elements[elementId] ?? 0) + elementGain,
          };
          const spawnAccumulators = {
            ...state.spawnAccumulators,
            [elementId]: 0,
          };
          result = { success: true, elementId, count: elementGain, goldGained };

          return {
            elements,
            spawnAccumulators,
            gold: state.gold + goldGained,
            dailyProgress: addDailyProgress(state.dailyProgress, {
              elementsCollected: elementGain,
              goldEarned: goldGained,
            }),
          };
        });

        return result;
      },
    }),
    {
      name: 'archetype-game-state',
      storage: throttledStorage,
      partialize: (state): PersistedState => ({
        activeTab: state.activeTab,
        elements: state.elements,
        discoveredElements: state.discoveredElements,
        gold: state.gold,
        elementLevels: state.elementLevels,
        comboCount: state.comboCount,
        comboExpiresAt: state.comboExpiresAt,
        lastTickAt: state.lastTickAt,
        activeBoosts: state.activeBoosts,
        claimedTierRewards: state.claimedTierRewards,
        dailyProgress: state.dailyProgress,
        activeStorm: state.activeStorm,
        adRewardProgress: state.adRewardProgress,
        spawnAccumulators: state.spawnAccumulators,
        language: state.language,
        bgmEnabled: state.bgmEnabled,
        prestigeCount: state.prestigeCount,
        prestigeGoldMultiplier: state.prestigeGoldMultiplier,
        prestigeSpawnMultiplier: state.prestigeSpawnMultiplier,
        prestigeTitle: state.prestigeTitle,
        unlockedAchievements: state.unlockedAchievements,
        permanentGoldMultiplier: state.permanentGoldMultiplier,
        permanentSpawnMultiplier: state.permanentSpawnMultiplier,
        achievementStats: state.achievementStats,
        tutorialStep: state.tutorialStep,
        tutorialCompleted: state.tutorialCompleted,
        welcomeGiftClaimed: state.welcomeGiftClaimed,
        sfxEnabled: state.sfxEnabled,
        hapticEnabled: state.hapticEnabled,
      }),
      onRehydrateStorage: () => (state) => {
        // 앱 재시작 시 localStorage에서 복원된 만료 부스트를 자동 정리
        if (!state) return;
        const now = Date.now();
        const validBoosts: Record<string, number> = {};
        for (const [key, expiresAt] of Object.entries(state.activeBoosts)) {
          if (expiresAt > now) validBoosts[key] = expiresAt;
        }
        state.activeBoosts = validBoosts;

        // achievementStats 누락 필드 기본값 보정 (구버전 저장 데이터 호환)
        if (!state.achievementStats) {
          state.achievementStats = {
            fuseCount: 0,
            enhanceCount: 0,
            offlineClaimCount: 0,
            prestigeCount: 0,
            tiersUnlocked: [],
          };
        }
        if (!Array.isArray(state.achievementStats.tiersUnlocked)) {
          state.achievementStats.tiersUnlocked = [];
        }
        if (!state.unlockedAchievements) state.unlockedAchievements = [];
        state.discoveredElements = getDiscoveredElementIds(state.discoveredElements, state.elements);
        if (!state.claimedTierRewards) state.claimedTierRewards = [];
        state.dailyProgress = ensureDailyProgress(state.dailyProgress);
        if (!state.activeStorm) state.activeStorm = null;
        state.adRewardProgress = ensureAdRewardProgress(state.adRewardProgress);
        if (!state.comboCount) state.comboCount = 0;
        if (!state.comboExpiresAt) state.comboExpiresAt = 0;
        if (!state.welcomeGiftClaimed) state.welcomeGiftClaimed = false;
        if (state.sfxEnabled === undefined) state.sfxEnabled = true;
        if (state.hapticEnabled === undefined) state.hapticEnabled = true;
        if (!state.permanentGoldMultiplier) state.permanentGoldMultiplier = 1.0;
        if (!state.permanentSpawnMultiplier) state.permanentSpawnMultiplier = 1.0;
        if (!state.prestigeCount) state.prestigeCount = 0;
        if (!state.prestigeGoldMultiplier) state.prestigeGoldMultiplier = 1.0;
        if (!state.prestigeSpawnMultiplier) state.prestigeSpawnMultiplier = 1.0;
        if (!state.prestigeTitle) state.prestigeTitle = '';
      },
    }
  )
);
