//
//  LifeSummary.swift
//  lichunWebsocket
//
//  Lifecycle "spine" models: end-of-life summary, generational legacy /
//  family tree, offline welcome-back digest, and life goals.
//
//  Synced with backend shapes:
//    - LifeSummary / FamilyTreeEntry / HeirInfo / LegacyData
//        (server/src/services/health/health_manager.ts)
//    - OfflineDigest (server/src/models/Player.ts)
//    - lifeGoalsUpdate payload (server/src/services/retention/integration.ts
//        + lifeGoals.ts)
//
//  All fields are tolerant of absence: older saves / partial payloads decode
//  cleanly. Keep these structs Codable so they can be lifted straight out of
//  the playerObject JSON and the dedicated lifecycle messages.
//

import Foundation

// MARK: - Life Summary

/// Built at the moment of death and surfaced via `lifeSummaryEvent` (online)
/// or the persisted `player.lifeSummary` field (offline death, on reconnect).
struct LifeSummary: Codable, Equatable {
    var finalAge: Int = 0
    var netWorth: Int = 0
    var peakCareer: PeakCareer?
    var relationshipsCount: Int = 0
    var childrenCount: Int = 0
    var notableEvents: [String] = []
    var achievementsEarned: Int = 0
    var lifetimeEarnings: Int = 0
    var score: Int = 0
    var diedAt: String = ""
    /// Generational legacy payload (heir + inheritance + family tree).
    var legacy: LegacyData?

    enum CodingKeys: String, CodingKey {
        case finalAge, netWorth, peakCareer, relationshipsCount, childrenCount
        case notableEvents, achievementsEarned, lifetimeEarnings, score, diedAt, legacy
    }

    init() {}

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        finalAge = (try? c.decodeIfPresent(Int.self, forKey: .finalAge)) ?? 0
        netWorth = (try? c.decodeIfPresent(Int.self, forKey: .netWorth)) ?? 0
        peakCareer = try? c.decodeIfPresent(PeakCareer.self, forKey: .peakCareer)
        relationshipsCount = (try? c.decodeIfPresent(Int.self, forKey: .relationshipsCount)) ?? 0
        childrenCount = (try? c.decodeIfPresent(Int.self, forKey: .childrenCount)) ?? 0
        notableEvents = (try? c.decodeIfPresent([String].self, forKey: .notableEvents)) ?? []
        achievementsEarned = (try? c.decodeIfPresent(Int.self, forKey: .achievementsEarned)) ?? 0
        lifetimeEarnings = (try? c.decodeIfPresent(Int.self, forKey: .lifetimeEarnings)) ?? 0
        score = (try? c.decodeIfPresent(Int.self, forKey: .score)) ?? 0
        diedAt = (try? c.decodeIfPresent(String.self, forKey: .diedAt)) ?? ""
        legacy = try? c.decodeIfPresent(LegacyData.self, forKey: .legacy)
    }
}

struct PeakCareer: Codable, Equatable {
    var title: String = ""
    var bestIncome: Int = 0
}

// MARK: - Generational Legacy

/// The heir designate (eldest living child) surfaced so the death screen can
/// offer "Continue as <name>".
struct HeirInfo: Codable, Equatable, Identifiable {
    var id: String = ""
    var name: String = ""
    var sex: String = ""
    var ageYears: Int = 0
    var affinity: Int = 0
}

/// Legacy payload embedded in `LifeSummary.legacy`: whether an heir exists, how
/// much wealth + prestige carries forward, and the full family tree.
struct LegacyData: Codable, Equatable {
    var heir: HeirInfo?
    var inheritance: Int = 0
    var familyPrestige: Int = 0
    var prestigeGained: Int = 0
    var familyTree: [FamilyTreeEntry] = []

    enum CodingKeys: String, CodingKey {
        case heir, inheritance, familyPrestige, prestigeGained, familyTree
    }

    init() {}

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        heir = try? c.decodeIfPresent(HeirInfo.self, forKey: .heir)
        inheritance = (try? c.decodeIfPresent(Int.self, forKey: .inheritance)) ?? 0
        familyPrestige = (try? c.decodeIfPresent(Int.self, forKey: .familyPrestige)) ?? 0
        prestigeGained = (try? c.decodeIfPresent(Int.self, forKey: .prestigeGained)) ?? 0
        familyTree = (try? c.decodeIfPresent([FamilyTreeEntry].self, forKey: .familyTree)) ?? []
    }
}

/// One ancestor's record in the persistent family tree. Appended on each death
/// and carried forward across the per-life wipe.
struct FamilyTreeEntry: Codable, Equatable, Identifiable {
    var name: String = ""
    var sex: String = ""
    var finalAge: Int = 0
    var peakCareer: String?
    var score: Int = 0
    var netWorth: Int = 0
    var diedAt: String = ""
    var generation: Int = 0

    // Stable identity for SwiftUI ForEach: generation is unique within a tree.
    var id: Int { generation }

    enum CodingKeys: String, CodingKey {
        case name, sex, finalAge, peakCareer, score, netWorth, diedAt, generation
    }

    init() {}

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        name = (try? c.decodeIfPresent(String.self, forKey: .name)) ?? ""
        sex = (try? c.decodeIfPresent(String.self, forKey: .sex)) ?? ""
        finalAge = (try? c.decodeIfPresent(Int.self, forKey: .finalAge)) ?? 0
        peakCareer = try? c.decodeIfPresent(String.self, forKey: .peakCareer)
        score = (try? c.decodeIfPresent(Int.self, forKey: .score)) ?? 0
        netWorth = (try? c.decodeIfPresent(Int.self, forKey: .netWorth)) ?? 0
        diedAt = (try? c.decodeIfPresent(String.self, forKey: .diedAt)) ?? ""
        generation = (try? c.decodeIfPresent(Int.self, forKey: .generation)) ?? 0
    }
}

// MARK: - Offline Welcome-Back Digest

/// Compact summary of what happened while the player was offline. Arrives both
/// inside `playerObject.offlineStats.digest` and as a dedicated `offlineDigest`
/// message (consumed once per offline period).
struct OfflineDigest: Codable, Equatable {
    var minutesAway: Int = 0
    var moneyDelta: Int = 0
    var ageYearsDelta: Int = 0
    var notableEvents: [String] = []
    var generatedAt: String = ""

    enum CodingKeys: String, CodingKey {
        case minutesAway, moneyDelta, ageYearsDelta, notableEvents, generatedAt
    }

    init() {}

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        minutesAway = (try? c.decodeIfPresent(Int.self, forKey: .minutesAway)) ?? 0
        moneyDelta = (try? c.decodeIfPresent(Int.self, forKey: .moneyDelta)) ?? 0
        ageYearsDelta = (try? c.decodeIfPresent(Int.self, forKey: .ageYearsDelta)) ?? 0
        notableEvents = (try? c.decodeIfPresent([String].self, forKey: .notableEvents)) ?? []
        generatedAt = (try? c.decodeIfPresent(String.self, forKey: .generatedAt)) ?? ""
    }

    /// Human-readable time-away phrasing for the welcome-back card.
    var timeAwayDescription: String {
        let minutes = max(0, minutesAway)
        if minutes < 60 {
            return "\(minutes) min away"
        }
        let hours = minutes / 60
        if hours < 24 {
            let remMin = minutes % 60
            return remMin > 0 ? "\(hours)h \(remMin)m away" : "\(hours)h away"
        }
        let days = hours / 24
        let remHours = hours % 24
        return remHours > 0 ? "\(days)d \(remHours)h away" : "\(days)d away"
    }
}

// MARK: - Life Goals (Wave 1: decode + cache only; UI is Wave 2)

/// Snapshot of the player's life goals / aspirations. Decoded and cached on
/// `lifeGoalsUpdate`; the UI that surfaces these is built in Wave 2.
struct LifeGoalsSnapshot: Codable, Equatable {
    var active: [LifeGoal] = []
    var completed: [LifeGoal] = []
    var lifeScore: Int = 0
    var justCompleted: [LifeGoal] = []

    enum CodingKeys: String, CodingKey {
        case active, completed, lifeScore, justCompleted
    }

    init() {}

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        active = (try? c.decodeIfPresent([LifeGoal].self, forKey: .active)) ?? []
        completed = (try? c.decodeIfPresent([LifeGoal].self, forKey: .completed)) ?? []
        lifeScore = (try? c.decodeIfPresent(Int.self, forKey: .lifeScore)) ?? 0
        justCompleted = (try? c.decodeIfPresent([LifeGoal].self, forKey: .justCompleted)) ?? []
    }
}

struct LifeGoal: Codable, Equatable, Identifiable {
    var id: String = ""
    var title: String = ""
    var description: String = ""
    var icon: String = ""
    var target: Int = 0
    var reward: Int = 0
    var lifeScore: Int = 0
    var current: Int = 0
    var progressPercent: Int = 0

    enum CodingKeys: String, CodingKey {
        case id, title, description, icon, target, reward, lifeScore, current, progressPercent
    }

    init() {}

    init(from decoder: Decoder) throws {
        let c = try decoder.container(keyedBy: CodingKeys.self)
        id = (try? c.decodeIfPresent(String.self, forKey: .id)) ?? ""
        title = (try? c.decodeIfPresent(String.self, forKey: .title)) ?? ""
        description = (try? c.decodeIfPresent(String.self, forKey: .description)) ?? ""
        icon = (try? c.decodeIfPresent(String.self, forKey: .icon)) ?? ""
        target = (try? c.decodeIfPresent(Int.self, forKey: .target)) ?? 0
        reward = (try? c.decodeIfPresent(Int.self, forKey: .reward)) ?? 0
        lifeScore = (try? c.decodeIfPresent(Int.self, forKey: .lifeScore)) ?? 0
        current = (try? c.decodeIfPresent(Int.self, forKey: .current)) ?? 0
        progressPercent = (try? c.decodeIfPresent(Int.self, forKey: .progressPercent)) ?? 0
    }
}
