///|
/// Semantic version of the public scheduling model.
pub const SCHEMA_VERSION : String = "neighbor-rota.schedule/v1"

///|
/// Severity attached to a stable diagnostic code.
pub(all) enum DiagnosticLevel {
  Info
  Warning
  Error
} derive(Debug, Eq)

///|
/// A validation or solver diagnostic suitable for machine processing.
pub(all) struct Diagnostic {
  level : DiagnosticLevel
  code : String
  message : String
  entity_id : String?
  field : String?
} derive(Debug, Eq)

///|
/// Build an informational diagnostic.
pub fn info_diagnostic(code : String, message : String) -> Diagnostic {
  { level: Info, code, message, entity_id: None, field: None }
}

///|
/// Build a warning diagnostic.
pub fn warning_diagnostic(code : String, message : String) -> Diagnostic {
  { level: Warning, code, message, entity_id: None, field: None }
}

///|
/// Build an error diagnostic.
pub fn error_diagnostic(code : String, message : String) -> Diagnostic {
  { level: Error, code, message, entity_id: None, field: None }
}

///|
/// Attach a domain entity identifier to a diagnostic.
pub fn Diagnostic::for_entity(self : Diagnostic, id : String) -> Diagnostic {
  { ..self, entity_id: Some(id) }
}

///|
/// Attach an input field name to a diagnostic.
pub fn Diagnostic::at_field(self : Diagnostic, field : String) -> Diagnostic {
  { ..self, field: Some(field) }
}

///|
/// A half-open minute range `[start, end)` within one planning horizon.
pub(all) struct TimeWindow {
  start_minute : Int
  end_minute : Int
} derive(Debug, Eq)

///|
/// Construct a time window without silently normalizing invalid values.
pub fn time_window(start_minute : Int, end_minute : Int) -> TimeWindow {
  { start_minute, end_minute }
}

///|
/// Duration of the window in minutes. Invalid windows report zero here.
pub fn TimeWindow::duration(self : TimeWindow) -> Int {
  if self.end_minute > self.start_minute {
    self.end_minute - self.start_minute
  } else {
    0
  }
}

///|
/// Whether two half-open windows overlap.
pub fn TimeWindow::overlaps(self : TimeWindow, other : TimeWindow) -> Bool {
  self.start_minute < other.end_minute && other.start_minute < self.end_minute
}

///|
/// Whether this window fully contains another window.
pub fn TimeWindow::contains(self : TimeWindow, other : TimeWindow) -> Bool {
  self.start_minute <= other.start_minute && self.end_minute >= other.end_minute
}

///|
/// Clamp a requested start so a service duration remains inside the window.
pub fn TimeWindow::latest_start(self : TimeWindow, duration : Int) -> Int {
  self.end_minute - duration
}

///|
/// Integer coordinates used for deterministic travel estimates.
pub(all) struct Location {
  id : String
  x : Int
  y : Int
  zone : String
} derive(Debug, Eq)

///|
/// Create a location in a named service zone.
pub fn location(id : String, x : Int, y : Int, zone : String) -> Location {
  { id, x, y, zone }
}

///|
/// A capability that may be required by a service visit.
pub(all) struct Skill {
  name : String
  level : Int
} derive(Debug, Eq)

///|
/// Create a named skill with a positive proficiency level.
pub fn skill(name : String, level : Int) -> Skill {
  { name, level }
}

///|
/// Worker classifications are useful for reporting and policy decisions.
pub(all) enum WorkerKind {
  Volunteer
  CareWorker
  Coordinator
} derive(Debug, Eq)

///|
/// A person who may be assigned one or more visits.
pub(all) struct Worker {
  id : String
  display_name : String
  kind : WorkerKind
  skills : Array[Skill]
  availability : Array[TimeWindow]
  home : Location
  max_minutes : Int
  max_visits : Int
  min_break_minutes : Int
  preferred_zones : Array[String]
  unavailable : Bool
} derive(Debug, Eq)

///|
/// Construct a worker with conservative defaults.
pub fn worker(
  id : String,
  display_name : String,
  kind : WorkerKind,
  home : Location,
) -> Worker {
  {
    id,
    display_name,
    kind,
    skills: [],
    availability: [],
    home,
    max_minutes: 480,
    max_visits: 8,
    min_break_minutes: 10,
    preferred_zones: [],
    unavailable: false,
  }
}

///|
/// Return the worker's level for a named skill, or zero when absent.
pub fn Worker::skill_level(self : Worker, name : String) -> Int {
  for item in self.skills {
    if item.name == name {
      return item.level
    }
  }
  0
}

///|
/// Whether the worker has a required skill at the requested level.
pub fn Worker::has_skill(self : Worker, required : Skill) -> Bool {
  self.skill_level(required.name) >= required.level
}

///|
/// Whether the worker is available for an entire interval.
pub fn Worker::is_available(self : Worker, interval : TimeWindow) -> Bool {
  if self.unavailable {
    return false
  }
  for window in self.availability {
    if window.contains(interval) {
      return true
    }
  }
  false
}

///|
/// Service urgency. Higher values receive stronger scheduling preference.
pub(all) enum Priority {
  Low
  Normal
  High
  Critical
} derive(Debug, Eq)

///|
/// A visit requested by a community service recipient.
pub(all) struct Visit {
  id : String
  recipient_id : String
  title : String
  location : Location
  window : TimeWindow
  duration_minutes : Int
  required_skills : Array[Skill]
  priority : Priority
  preferred_worker_ids : Array[String]
  forbidden_worker_ids : Array[String]
  continuity_group : String?
  required : Bool
} derive(Debug, Eq)

///|
/// Construct a visit with no skill or continuity requirements.
pub fn visit(
  id : String,
  recipient_id : String,
  title : String,
  location : Location,
  window : TimeWindow,
  duration_minutes : Int,
) -> Visit {
  {
    id,
    recipient_id,
    title,
    location,
    window,
    duration_minutes,
    required_skills: [],
    priority: Normal,
    preferred_worker_ids: [],
    forbidden_worker_ids: [],
    continuity_group: None,
    required: true,
  }
}

///|
/// Whether a worker identifier is explicitly forbidden for this visit.
pub fn Visit::forbids(self : Visit, worker_id : String) -> Bool {
  string_array_contains(self.forbidden_worker_ids, worker_id)
}

///|
/// Whether a worker identifier is explicitly preferred for this visit.
pub fn Visit::prefers(self : Visit, worker_id : String) -> Bool {
  string_array_contains(self.preferred_worker_ids, worker_id)
}

///|
/// One fixed assignment in a schedule.
pub(all) struct Assignment {
  visit_id : String
  worker_id : String
  start_minute : Int
  end_minute : Int
  travel_before_minutes : Int
  score : Int
  reasons : Array[String]
} derive(Debug, Eq)

///|
/// A visit that could not be placed, with stable reason codes.
pub(all) struct UnassignedVisit {
  visit_id : String
  reason_codes : Array[String]
  details : Array[String]
} derive(Debug, Eq)

///|
/// Aggregate dimensions used to compare schedules.
pub(all) struct ScheduleScore {
  total : Int
  assigned_priority : Int
  travel_penalty : Int
  fairness_penalty : Int
  continuity_bonus : Int
  preference_bonus : Int
  disruption_penalty : Int
} derive(Debug, Eq)

///|
/// Complete auditable scheduling result.
pub(all) struct Schedule {
  schema : String
  assignments : Array[Assignment]
  unassigned : Array[UnassignedVisit]
  diagnostics : Array[Diagnostic]
  score : ScheduleScore
  feasible : Bool
} derive(Debug, Eq)

///|
/// Policy weights and operational limits.
pub(all) struct SchedulePolicy {
  travel_minutes_per_unit : Int
  cross_zone_penalty_minutes : Int
  priority_weight : Int
  preference_weight : Int
  continuity_weight : Int
  travel_weight : Int
  fairness_weight : Int
  disruption_weight : Int
  allow_optional_unassigned : Bool
  local_search_rounds : Int
} derive(Debug, Eq)

///|
/// Balanced policy for the built-in demonstrations.
pub fn default_policy() -> SchedulePolicy {
  {
    travel_minutes_per_unit: 5,
    cross_zone_penalty_minutes: 15,
    priority_weight: 100,
    preference_weight: 30,
    continuity_weight: 40,
    travel_weight: 2,
    fairness_weight: 1,
    disruption_weight: 50,
    allow_optional_unassigned: true,
    local_search_rounds: 8,
  }
}

///|
/// All inputs required for a deterministic scheduling run.
pub(all) struct ScheduleRequest {
  workers : Array[Worker]
  visits : Array[Visit]
  policy : SchedulePolicy
  baseline : Schedule?
} derive(Debug, Eq)

///|
/// Create a request without a previous schedule.
pub fn schedule_request(
  workers : Array[Worker],
  visits : Array[Visit],
) -> ScheduleRequest {
  { workers, visits, policy: default_policy(), baseline: None }
}

///|
/// Stable human-readable name for diagnostic levels.
pub fn diagnostic_level_name(level : DiagnosticLevel) -> String {
  match level {
    Info => "info"
    Warning => "warning"
    Error => "error"
  }
}

///|
/// Stable human-readable name for worker kinds.
pub fn worker_kind_name(kind : WorkerKind) -> String {
  match kind {
    Volunteer => "volunteer"
    CareWorker => "care_worker"
    Coordinator => "coordinator"
  }
}

///|
/// Stable human-readable name for priority values.
pub fn priority_name(priority : Priority) -> String {
  match priority {
    Low => "low"
    Normal => "normal"
    High => "high"
    Critical => "critical"
  }
}

///|
/// Numeric priority used by deterministic ordering and scoring.
pub fn priority_value(priority : Priority) -> Int {
  match priority {
    Low => 1
    Normal => 2
    High => 4
    Critical => 8
  }
}