import 'package:drift/drift.dart'; // 11 user-data tables + habit_dose_variants (normalized child per ADR-0002). class Users extends Table { TextColumn get id => text()(); TextColumn get displayName => text().named('display_name').nullable()(); TextColumn get locale => text().withDefault(const Constant('ko-KR'))(); TextColumn get timezone => text().withDefault(const Constant('Asia/Seoul'))(); TextColumn get createdAt => text().named('created_at')(); TextColumn get preferencesJson => text().named('preferences_json').nullable()(); @override Set get primaryKey => {id}; } class Phases extends Table { TextColumn get id => text()(); TextColumn get userId => text().named('user_id').customConstraint('REFERENCES users(id) NOT NULL')(); TextColumn get title => text().nullable()(); TextColumn get startedAt => text().named('started_at')(); TextColumn get endedAt => text().named('ended_at').nullable()(); IntColumn get durationWeeks => integer() .named('duration_weeks') .withDefault(const Constant(6)) .check(const CustomExpression("duration_weeks >= 1"))(); TextColumn get status => text().check(const CustomExpression( "status IN ('active','completed','abandoned')"))(); TextColumn get intentionText => text().named('intention_text').nullable()(); BoolColumn get rewardDeclarationsLocked => boolean() .named('reward_declarations_locked') .withDefault(const Constant(false))(); @override Set get primaryKey => {id}; } class Habits extends Table { TextColumn get id => text()(); TextColumn get userId => text().named('user_id').customConstraint('REFERENCES users(id) NOT NULL')(); TextColumn get phaseId => text() .named('phase_id') .nullable() .customConstraint('NULL REFERENCES phases(id) ON DELETE SET NULL')(); TextColumn get type => text().check(const CustomExpression("type IN ('build','break')"))(); TextColumn get status => text().check(const CustomExpression( "status IN ('active','paused','completed','abandoned')"))(); TextColumn get title => text()(); TextColumn get protocolId => text() .named('protocol_id') .nullable() .customConstraint('NULL REFERENCES protocols(id)')(); TextColumn get breakProtocolId => text() .named('break_protocol_id') .nullable() .customConstraint('NULL REFERENCES break_protocols(id)')(); TextColumn get commonFrameIdsJson => text().named('common_frame_ids_json').nullable()(); TextColumn get frameLevel => text().named('frame_level').check( const CustomExpression("frame_level IN ('L2','L3')"))(); // R3 TextColumn get frameOriginalText => text().named('frame_original_text').nullable()(); TextColumn get frameFramedText => text().named('frame_framed_text')(); TextColumn get anchorWhen => text().named('anchor_when').nullable()(); TextColumn get anchorAfterWhat => text().named('anchor_after_what').nullable()(); TextColumn get anchorWhere => text().named('anchor_where').nullable()(); IntColumn get stackPosition => integer().named('stack_position').nullable().check( const CustomExpression("stack_position IS NULL OR stack_position >= 1"))(); TextColumn get minDose => text().named('min_dose').nullable()(); TextColumn get targetDose => text().named('target_dose').nullable()(); TextColumn get startedAt => text().named('started_at')(); TextColumn get endedAt => text().named('ended_at').nullable()(); TextColumn get tagsJson => text().named('tags_json').nullable()(); @override Set get primaryKey => {id}; @override List get customConstraints => [ // XOR: build → protocol_id, break → break_protocol_id "CHECK ((type = 'build' AND protocol_id IS NOT NULL AND break_protocol_id IS NULL) " "OR (type = 'break' AND break_protocol_id IS NOT NULL AND protocol_id IS NULL))", ]; } class HabitDoseVariants extends Table { TextColumn get variantId => text().named('variant_id')(); TextColumn get habitId => text() .named('habit_id') .customConstraint('REFERENCES habits(id) ON DELETE CASCADE NOT NULL')(); TextColumn get label => text()(); TextColumn get doseText => text().named('dose_text')(); TextColumn get contextTagsJson => text().named('context_tags_json').nullable()(); TextColumn get conditionTagsJson => text().named('condition_tags_json').nullable()(); BoolColumn get isMinimum => boolean().named('is_minimum').withDefault(const Constant(false))(); IntColumn get sortOrder => integer().named('sort_order').withDefault(const Constant(0))(); @override Set get primaryKey => {variantId}; } class IfThenRules extends Table { TextColumn get id => text()(); TextColumn get habitId => text() .named('habit_id') .customConstraint('REFERENCES habits(id) ON DELETE CASCADE NOT NULL')(); TextColumn get ifCondition => text().named('if_condition')(); TextColumn get thenAction => text().named('then_action')(); TextColumn get triggerType => text().named('trigger_type').nullable().check( const CustomExpression( "trigger_type IS NULL OR trigger_type IN ('time','location','emotion','preceding_action','urge')"))(); IntColumn get priority => integer() .withDefault(const Constant(1)) .check(const CustomExpression("priority BETWEEN 1 AND 3"))(); TextColumn get createdAt => text().named('created_at').nullable()(); @override Set get primaryKey => {id}; } class TrackerEntries extends Table { TextColumn get id => text()(); TextColumn get habitId => text() .named('habit_id') .customConstraint('REFERENCES habits(id) ON DELETE RESTRICT NOT NULL')(); TextColumn get date => text()(); TextColumn get value => text().check(const CustomExpression("value IN ('done','blank')"))(); // R5 TextColumn get loggedAt => text().named('logged_at').nullable()(); TextColumn get note => text().nullable().check( const CustomExpression("note IS NULL OR length(note) <= 200"))(); TextColumn get variantId => text() .named('variant_id') .nullable() .customConstraint( 'NULL REFERENCES habit_dose_variants(variant_id) ON DELETE SET NULL')(); TextColumn get ctxLocation => text().named('ctx_location').nullable()(); TextColumn get ctxCondition => text().named('ctx_condition').nullable()(); @override Set get primaryKey => {id}; } class LapseLogs extends Table { TextColumn get id => text()(); TextColumn get habitId => text() .named('habit_id') .customConstraint('REFERENCES habits(id) NOT NULL')(); TextColumn get date => text()(); TextColumn get labelText => text().named('label_text')(); TextColumn get examineHaltJson => text().named('examine_halt_json')(); TextColumn get antecedentJson => text().named('antecedent_json')(); TextColumn get replan => text()(); TextColumn get nextAction => text().named('next_action')(); TextColumn get createdAt => text().named('created_at').nullable()(); @override Set get primaryKey => {id}; } class UrgeLogs extends Table { TextColumn get id => text()(); TextColumn get habitId => text() .named('habit_id') .customConstraint('REFERENCES habits(id) NOT NULL')(); TextColumn get occurredAt => text().named('occurred_at')(); IntColumn get intensityBefore => integer().named('intensity_before').nullable().check( const CustomExpression( "intensity_before IS NULL OR intensity_before BETWEEN 0 AND 10"))(); IntColumn get intensityAfter => integer().named('intensity_after').nullable().check( const CustomExpression( "intensity_after IS NULL OR intensity_after BETWEEN 0 AND 10"))(); IntColumn get durationSeconds => integer().named('duration_seconds').nullable().check( const CustomExpression( "duration_seconds IS NULL OR duration_seconds >= 0"))(); TextColumn get bodyLocationJson => text().named('body_location_json').nullable()(); BoolColumn get passed => boolean()(); TextColumn get methodUsed => text().named('method_used').nullable().check( const CustomExpression( "method_used IS NULL OR method_used IN ('cyclic_sighing','walk','water','social_contact','if_then_action','other')"))(); @override Set get primaryKey => {id}; } class RewardDeclarations extends Table { TextColumn get id => text()(); TextColumn get phaseId => text() .named('phase_id') .customConstraint('REFERENCES phases(id) NOT NULL')(); TextColumn get habitId => text() .named('habit_id') .customConstraint('REFERENCES habits(id) NOT NULL')(); TextColumn get tier => text().check( const CustomExpression("tier IN ('T0','T1','T2','T3','T4')"))(); TextColumn get milestoneRule => text().named('milestone_rule')(); TextColumn get milestoneMachineRuleJson => text().named('milestone_machine_rule_json').nullable()(); TextColumn get rewardText => text().named('reward_text')(); TextColumn get rewardMenuItemId => text() .named('reward_menu_item_id') .nullable() .customConstraint('NULL REFERENCES reward_menu_items(id)')(); IntColumn get estimatedCostKrw => integer().named('estimated_cost_krw').nullable().check( const CustomExpression( "estimated_cost_krw IS NULL OR estimated_cost_krw >= 0"))(); BoolColumn get isEffortTied => boolean().named('is_effort_tied').nullable()(); TextColumn get declaredAt => text().named('declared_at')(); @override Set get primaryKey => {id}; } class RewardClaims extends Table { TextColumn get id => text()(); TextColumn get declarationId => text() .named('declaration_id') .customConstraint('REFERENCES reward_declarations(id) NOT NULL')(); TextColumn get milestoneReachedAt => text().named('milestone_reached_at')(); BoolColumn get fulfilled => boolean()(); TextColumn get fulfilledAt => text().named('fulfilled_at').nullable()(); TextColumn get reflection => text().nullable().check( const CustomExpression( "reflection IS NULL OR length(reflection) <= 500"))(); @override Set get primaryKey => {id}; } class Reflections extends Table { TextColumn get id => text()(); TextColumn get userId => text().named('user_id').customConstraint('REFERENCES users(id) NOT NULL')(); TextColumn get scope => text().check(const CustomExpression( "scope IN ('weekly','monthly','phase_end')"))(); TextColumn get periodStart => text().named('period_start')(); TextColumn get periodEnd => text().named('period_end')(); TextColumn get phaseId => text() .named('phase_id') .nullable() .customConstraint('NULL REFERENCES phases(id) ON DELETE SET NULL')(); TextColumn get kept => text().nullable()(); TextColumn get missed => text().nullable()(); TextColumn get adjust => text().nullable()(); TextColumn get identityNote => text().named('identity_note').nullable()(); RealColumn get minimumRatio => real().named('minimum_ratio').nullable().check( const CustomExpression( "minimum_ratio IS NULL OR (minimum_ratio BETWEEN 0 AND 1)"))(); TextColumn get createdAt => text().named('created_at').nullable()(); @override Set get primaryKey => {id}; } class MetaKv extends Table { TextColumn get key => text()(); TextColumn get value => text()(); @override Set get primaryKey => {key}; }