// moonbit-4d-schedule - Core Domain Types

///|
pub(all) struct Date {
  year : Int
  month : Int
  day : Int
} derive(Eq, Compare)

///|
pub(all) struct DateTime {
  year : Int
  month : Int
  day : Int
  hour : Int
  minute : Int
  second : Int
} derive(Eq, Compare)

///|
pub(all) enum DayOfWeek {
  Monday
  Tuesday
  Wednesday
  Thursday
  Friday
  Saturday
  Sunday
} derive(Eq)

///|
pub(all) struct TimeShift {
  name : String
  start_hour : Int
  end_hour : Int
} derive(Eq)

///|
pub(all) struct WorkCalendar {
  id : String
  name : String
  working_days : Array[DayOfWeek]
  shifts : Array[TimeShift]
  holidays : Array[Date]
} derive(Eq)

///|
pub(all) enum DependencyType {
  FS // Finish-to-Start
  SS // Start-to-Start
  FF // Finish-to-Finish
  SF // Start-to-Finish
} derive(Eq)

///|
pub(all) enum ConstraintType {
  AsSoonAsPossible
  AsLateAsPossible
  MustStartOn
  MustFinishOn
  StartNoEarlierThan
  FinishNoLaterThan
} derive(Eq)

///|
pub(all) struct ActivityConstraint {
  constraint_type : ConstraintType
  target_date : Date?
} derive(Eq)

///|
pub(all) enum ActivityStatus {
  NotStarted
  InProgress
  Completed
  Delayed
} derive(Eq)

///|
pub(all) struct ActivityPredecessor {
  activity_id : String
  dep_type : DependencyType
  lag_days : Double
} derive(Eq)

///|
pub(all) struct Activity {
  id : String
  code : String
  name : String
  duration : Double
  optimistic_duration : Double
  pessimistic_duration : Double
  calendar_id : String
  constraint : ActivityConstraint
  predecessors : Array[ActivityPredecessor]
  element_ids : Array[String]
} derive(Eq)

///|
pub(all) struct BimVector3 {
  x : Double
  y : Double
  z : Double
} derive(Eq)

///|
pub(all) struct SpatialBounds {
  min_point : BimVector3
  max_point : BimVector3
} derive(Eq)

///|
pub(all) enum BimCategory {
  Foundation
  Column
  Beam
  Slab
  Wall
  Roof
  DoorWindow
  MEP
  Equipment
  Finishing
  CustomCategory(String)
} derive(Eq)

///|
pub(all) struct BimElement {
  guid : String
  name : String
  category : BimCategory
  level : String
  zone : String
  bounds : SpatialBounds
  volume : Double
} derive(Eq)

///|
pub(all) enum ResourceKind {
  Labor
  Equipment
  Material
} derive(Eq)

///|
pub(all) struct Resource {
  id : String
  code : String
  name : String
  kind : ResourceKind
  unit : String
  max_capacity : Double
  cost_per_unit : Double
} derive(Eq)

///|
pub(all) struct ResourceDemand {
  activity_id : String
  resource_id : String
  units_per_day : Double
} derive(Eq)

///|
pub(all) struct FloatInfo {
  early_start : Double
  early_finish : Double
  late_start : Double
  late_finish : Double
  total_float : Double
  free_float : Double
  is_critical : Bool
  drag : Double
} derive(Eq)

///|
pub(all) struct ElementState {
  guid : String
  activity_id : String
  status : ActivityStatus
  completion_ratio : Double
  planned_start : Double
  planned_finish : Double
  actual_start : Double?
  actual_finish : Double?
  hex_color : String
} derive(Eq)

///|
pub(all) struct StateSnapshot {
  timestamp : Double
  date_str : String
  elements : Array[ElementState]
  overall_completion : Double
  active_activity_count : Int
  delayed_activity_count : Int
} derive(Eq)

///|
pub(all) struct ScheduleProject {
  id : String
  name : String
  start_date : Date
  calendars : Array[WorkCalendar]
  activities : Array[Activity]
  elements : Array[BimElement]
  resources : Array[Resource]
  demands : Array[ResourceDemand]
} derive(Eq)

// --- Utility Constructor & Methods ---

///|
pub fn Date::new(year : Int, month : Int, day : Int) -> Date {
  { year, month, day }
}

///|
pub fn Date::to_iso_string(self : Date) -> String {
  let m = if self.month < 10 {
    "0" + self.month.to_string()
  } else {
    self.month.to_string()
  }
  let d = if self.day < 10 {
    "0" + self.day.to_string()
  } else {
    self.day.to_string()
  }
  self.year.to_string() + "-" + m + "-" + d
}

///|
pub fn Date::is_leap_year(year : Int) -> Bool {
  (year % 4 == 0 && year % 100 != 0) || year % 400 == 0
}

///|
pub fn Date::days_in_month(year : Int, month : Int) -> Int {
  match month {
    1 | 3 | 5 | 7 | 8 | 10 | 12 => 31
    4 | 6 | 9 | 11 => 30
    2 => if Date::is_leap_year(year) { 29 } else { 28 }
    _ => 30
  }
}

///|
pub fn Date::add_days(self : Date, days : Int) -> Date {
  let mut y = self.year
  let mut m = self.month
  let mut d = self.day
  let mut remaining = days
  while remaining > 0 {
    d = d + 1
    if d > Date::days_in_month(y, m) {
      d = 1
      m = m + 1
      if m > 12 {
        m = 1
        y = y + 1
      }
    }
    remaining = remaining - 1
  }
  while remaining < 0 {
    d = d - 1
    if d < 1 {
      m = m - 1
      if m < 1 {
        m = 12
        y = y - 1
      }
      d = Date::days_in_month(y, m)
    }
    remaining = remaining + 1
  }
  { year: y, month: m, day: d }
}

///|
pub fn Date::day_of_week(self : Date) -> DayOfWeek {
  // Zeller's Congruence algorithm
  let mut m = self.month
  let mut y = self.year
  if m < 3 {
    m = m + 12
    y = y - 1
  }
  let k = y % 100
  let j = y / 100
  let h = (self.day + 13 * (m + 1) / 5 + k + k / 4 + j / 4 + 5 * j) % 7
  match h {
    0 => DayOfWeek::Saturday
    1 => DayOfWeek::Sunday
    2 => DayOfWeek::Monday
    3 => DayOfWeek::Tuesday
    4 => DayOfWeek::Wednesday
    5 => DayOfWeek::Thursday
    _ => DayOfWeek::Friday
  }
}

///|
pub fn WorkCalendar::default_standard() -> WorkCalendar {
  {
    id: "CAL_STD",
    name: "Standard 5-Day Work Week",
    working_days: [
      DayOfWeek::Monday,
      DayOfWeek::Tuesday,
      DayOfWeek::Wednesday,
      DayOfWeek::Thursday,
      DayOfWeek::Friday,
    ],
    shifts: [{ name: "Standard Shift", start_hour: 8, end_hour: 17 }],
    holidays: [],
  }
}

///|
pub fn WorkCalendar::default_seven_day() -> WorkCalendar {
  {
    id: "CAL_7D",
    name: "Continuous 7-Day Work Week",
    working_days: [
      DayOfWeek::Monday,
      DayOfWeek::Tuesday,
      DayOfWeek::Wednesday,
      DayOfWeek::Thursday,
      DayOfWeek::Friday,
      DayOfWeek::Saturday,
      DayOfWeek::Sunday,
    ],
    shifts: [{ name: "Full Shift", start_hour: 8, end_hour: 18 }],
    holidays: [],
  }
}

///|
pub fn WorkCalendar::is_working_day(self : WorkCalendar, date : Date) -> Bool {
  let dow = date.day_of_week()
  let mut is_work_dow = false
  for d in self.working_days {
    if d == dow {
      is_work_dow = true
      break
    }
  }
  if !is_work_dow {
    return false
  }
  for h in self.holidays {
    if h.year == date.year && h.month == date.month && h.day == date.day {
      return false
    }
  }
  true
}

///|
pub fn BimCategory::to_string(self : BimCategory) -> String {
  match self {
    Foundation => "Foundation"
    Column => "Column"
    Beam => "Beam"
    Slab => "Slab"
    Wall => "Wall"
    Roof => "Roof"
    DoorWindow => "DoorWindow"
    MEP => "MEP"
    Equipment => "Equipment"
    Finishing => "Finishing"
    CustomCategory(s) => s
  }
}

///|
pub fn BimCategory::default_color(self : BimCategory) -> String {
  match self {
    Foundation => "#8B4513"
    Column => "#708090"
    Beam => "#A9A9A9"
    Slab => "#D3D3D3"
    Wall => "#BC8F8F"
    Roof => "#B22222"
    DoorWindow => "#87CEEB"
    MEP => "#FF8C00"
    Equipment => "#9370DB"
    Finishing => "#E6E6FA"
    CustomCategory(_) => "#4682B4"
  }
}

///|
pub fn ActivityStatus::to_color(self : ActivityStatus) -> String {
  match self {
    NotStarted => "#9CA3AF"
    InProgress => "#3B82F6"
    Completed => "#10B981"
    Delayed => "#EF4444"
  }
}