// // GuidedReflection.swift // Reflect // // Codable model for guided reflection responses, stored as JSON in MoodEntryModel. // import Foundation // MARK: - Mood Category enum MoodCategory: String, Codable { case positive // great, good → 3 questions (Behavioral Activation) case neutral // average → 4 questions (ACT Cognitive Defusion) case negative // bad, horrible → 4 questions (CBT Thought Record) init(from mood: Mood) { switch mood { case .great, .good: self = .positive case .average: self = .neutral case .horrible, .bad: self = .negative default: self = .neutral } } var questionCount: Int { switch self { case .positive: return 3 case .neutral, .negative: return 4 } } /// The therapeutic technique name for display purposes. var techniqueName: String { switch self { case .positive: return "Behavioral Activation" case .neutral: return "Acceptance & Commitment Therapy" case .negative: return "Cognitive Behavioral Therapy" } } /// Short CBT step labels shown above each question. var stepLabels: [String] { switch self { case .negative: return [ String(localized: "Situation"), String(localized: "Automatic Thought"), String(localized: "Perspective Check"), String(localized: "Reframe"), ] case .neutral: return [ String(localized: "Awareness"), String(localized: "Thought"), String(localized: "Defusion"), String(localized: "Values"), ] case .positive: return [ String(localized: "Activity"), String(localized: "Awareness"), String(localized: "Planning"), ] } } } // MARK: - Question Chips struct QuestionChips { let topRow: [String] let expanded: [String] var hasExpanded: Bool { !expanded.isEmpty } static func chips(for category: MoodCategory, questionIndex: Int) -> QuestionChips? { switch (category, questionIndex) { // MARK: Positive (Great/Good) — Behavioral Activation // Q1: "What did you do today…?" — no chips (situational) // Q2: "What thought or moment stands out?" — memorable moments to savor case (.positive, 1): return QuestionChips( topRow: [ String(localized: "guided_chip_pos_moment_conversation"), String(localized: "guided_chip_pos_moment_accomplished"), String(localized: "guided_chip_pos_moment_calm"), String(localized: "guided_chip_pos_moment_laugh"), String(localized: "guided_chip_pos_moment_grateful_person"), String(localized: "guided_chip_pos_moment_small_win"), ], expanded: [ String(localized: "guided_chip_pos_moment_beauty"), String(localized: "guided_chip_pos_moment_connected"), String(localized: "guided_chip_pos_moment_progress"), String(localized: "guided_chip_pos_moment_like_myself"), String(localized: "guided_chip_pos_moment_kindness"), String(localized: "guided_chip_pos_moment_time_well_spent"), ] ) // Q3: "How could you create more days like this?" — reinforcing actions case (.positive, 2): return QuestionChips( topRow: [ String(localized: "guided_chip_pos_act_more_of_this"), String(localized: "guided_chip_pos_act_time_with_people"), String(localized: "guided_chip_pos_act_get_outside"), String(localized: "guided_chip_pos_act_stay_active"), String(localized: "guided_chip_pos_act_keep_routine"), String(localized: "guided_chip_pos_act_practice_gratitude"), ], expanded: [ String(localized: "guided_chip_pos_act_celebrate_wins"), String(localized: "guided_chip_pos_act_hobbies"), String(localized: "guided_chip_pos_act_help_someone"), String(localized: "guided_chip_pos_act_say_yes"), String(localized: "guided_chip_pos_act_rest"), String(localized: "guided_chip_pos_act_limit_doomscroll"), ] ) // MARK: Neutral (Average) — ACT Cognitive Defusion // Q1: "What feeling has been sitting with you?" — ambivalent feelings case (.neutral, 0): return QuestionChips( topRow: [ String(localized: "guided_chip_neu_boredom"), String(localized: "guided_chip_neu_restlessness"), String(localized: "guided_chip_neu_uncertainty"), String(localized: "guided_chip_neu_numbness"), String(localized: "guided_chip_neu_indifference"), String(localized: "guided_chip_neu_distraction"), ], expanded: [ String(localized: "guided_chip_neu_flatness"), String(localized: "guided_chip_neu_disconnection"), String(localized: "guided_chip_neu_ambivalence"), String(localized: "guided_chip_neu_weariness"), String(localized: "guided_chip_neu_apathy"), String(localized: "guided_chip_neu_autopilot"), ] ) // Q2: "What thought is connected to that feeling?" — no chips (specific thought) // Q3: "Is that thought true, or something your mind is telling you?" — defusion reframes case (.neutral, 2): return QuestionChips( topRow: [ String(localized: "guided_chip_neu_def_just_a_thought"), String(localized: "guided_chip_neu_def_storytelling"), String(localized: "guided_chip_neu_def_thought_passed"), String(localized: "guided_chip_neu_def_more_weight"), String(localized: "guided_chip_neu_def_not_helpful"), String(localized: "guided_chip_neu_def_notice_not_act"), String(localized: "guided_chip_neu_def_dont_solve_now"), String(localized: "guided_chip_neu_def_doesnt_define"), ], expanded: [] ) // Q4: "What matters to you about tomorrow?" — values-aligned intentions case (.neutral, 3): return QuestionChips( topRow: [ String(localized: "guided_chip_neu_val_be_present"), String(localized: "guided_chip_neu_val_connect"), String(localized: "guided_chip_neu_val_body"), String(localized: "guided_chip_neu_val_meaningful_work"), String(localized: "guided_chip_neu_val_kind_to_self"), String(localized: "guided_chip_neu_val_small_goal"), ], expanded: [ String(localized: "guided_chip_neu_val_less_autopilot"), String(localized: "guided_chip_neu_val_one_thing_better"), String(localized: "guided_chip_neu_val_ask_what_i_need"), String(localized: "guided_chip_neu_val_let_go"), ] ) // MARK: Negative (Bad/Horrible) — CBT Thought Record // Q1: "What happened today…?" — no chips (situational) // Q2: "What thought kept coming back?" — common automatic negative thoughts case (.negative, 1): return QuestionChips( topRow: [ String(localized: "guided_chip_neg_not_good_enough"), String(localized: "guided_chip_neg_nothing_goes_right"), String(localized: "guided_chip_neg_all_my_fault"), String(localized: "guided_chip_neg_cant_handle"), String(localized: "guided_chip_neg_no_one_understands"), String(localized: "guided_chip_neg_should_have"), ], expanded: [ String(localized: "guided_chip_neg_never_change"), String(localized: "guided_chip_neg_falling_behind"), String(localized: "guided_chip_neg_dont_matter"), String(localized: "guided_chip_neg_something_wrong"), String(localized: "guided_chip_neg_letting_down"), String(localized: "guided_chip_neg_cant_do_right"), ] ) // Q3: "What would you tell a friend?" — compassionate reframes case (.negative, 2): return QuestionChips( topRow: [ String(localized: "guided_chip_neg_too_hard"), String(localized: "guided_chip_neg_one_bad_day"), String(localized: "guided_chip_neg_better_than_think"), String(localized: "guided_chip_neg_ok_to_struggle"), String(localized: "guided_chip_neg_feeling_will_pass"), String(localized: "guided_chip_neg_dont_need_figured"), String(localized: "guided_chip_neg_not_whole_story"), String(localized: "guided_chip_neg_give_grace"), ], expanded: [] ) // Q4: "More balanced way to see it?" — cognitive reframes first, grounding actions expanded case (.negative, 3): return QuestionChips( topRow: [ String(localized: "guided_chip_neg_act_worst_case"), String(localized: "guided_chip_neg_act_facts_feelings"), String(localized: "guided_chip_neg_act_matter_in_week"), String(localized: "guided_chip_neg_act_got_through"), String(localized: "guided_chip_neg_ref_one_chapter"), String(localized: "guided_chip_neg_ref_doing_my_best"), ], expanded: [ String(localized: "guided_chip_neg_act_talk_someone"), String(localized: "guided_chip_neg_act_write_it_out"), String(localized: "guided_chip_neg_act_take_walk"), String(localized: "guided_chip_neg_act_step_away"), String(localized: "guided_chip_neg_act_get_rest"), String(localized: "guided_chip_neg_act_one_small_thing"), ] ) default: return nil } } } // MARK: - Guided Reflection struct GuidedReflection: Codable, Equatable { struct Response: Codable, Equatable, Identifiable { var id: Int // question index (0-based) let question: String var answer: String var selectedChips: [String] = [] enum CodingKeys: String, CodingKey { case id, question, answer, selectedChips } init(id: Int, question: String, answer: String, selectedChips: [String] = []) { self.id = id self.question = question self.answer = answer self.selectedChips = selectedChips } init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) id = try container.decode(Int.self, forKey: .id) question = try container.decode(String.self, forKey: .question) answer = try container.decode(String.self, forKey: .answer) selectedChips = try container.decodeIfPresent([String].self, forKey: .selectedChips) ?? [] } } let moodCategory: MoodCategory var responses: [Response] var completedAt: Date? // MARK: - Computed Properties var isComplete: Bool { responses.count == moodCategory.questionCount && responses.allSatisfy { !$0.answer.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty } } var answeredCount: Int { responses.filter { !$0.answer.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty }.count } var totalQuestions: Int { moodCategory.questionCount } // MARK: - Factory static func createNew(for mood: Mood) -> GuidedReflection { let category = MoodCategory(from: mood) let questionTexts = questions(for: category) let responses = questionTexts.enumerated().map { index, question in Response(id: index, question: question, answer: "") } return GuidedReflection(moodCategory: category, responses: responses, completedAt: nil) } static func questions(for category: MoodCategory) -> [String] { switch category { case .positive: return [ String(localized: "guided_reflection_positive_q1"), String(localized: "guided_reflection_positive_q2"), String(localized: "guided_reflection_positive_q3"), ] case .neutral: return [ String(localized: "guided_reflection_neutral_q1"), String(localized: "guided_reflection_neutral_q2"), String(localized: "guided_reflection_neutral_q3"), String(localized: "guided_reflection_neutral_q4"), ] case .negative: return [ String(localized: "guided_reflection_negative_q1"), String(localized: "guided_reflection_negative_q2"), String(localized: "guided_reflection_negative_q3"), String(localized: "guided_reflection_negative_q4"), ] } } // MARK: - JSON Helpers func encode() -> String? { guard let data = try? JSONEncoder().encode(self) else { return nil } return String(data: data, encoding: .utf8) } static func decode(from json: String) -> GuidedReflection? { guard let data = json.data(using: .utf8) else { return nil } return try? JSONDecoder().decode(GuidedReflection.self, from: data) } }