///|
pub(all) suberror CalError {
  EmptyInput
  MissingColon(Int, String)
  BadPropertyName(Int, String)
  BadParameter(Int, String)
  UnexpectedEnd(String)
  MismatchedEnd(String, String)
  NestedCalendar
  MissingCalendar
  MultipleCalendars
  PropertyOutsideCalendar(String)
  ComponentOutsideCalendar(String)
}

///|
pub(all) enum Severity {
  Info
  Warn
  Deny
} derive(Debug, Eq)

///|
pub(all) enum ValueKind {
  Text
  Date
  DateTime
  Duration
  Uri
  Integer
  Boolean
  Float
  CalAddress
  Period
  Recurrence
  UtcOffset
  Unknown(String)
} derive(Debug, Eq)

///|
pub(all) enum ComponentRole {
  CalendarRole
  EventRole
  TodoRole
  JournalRole
  TimezoneRole
  ObservanceRole
  AlarmRole
  UnknownRole(String)
} derive(Debug, Eq)

///|
pub(all) enum PropertyCardinality {
  RequiredOnce
  OptionalOnce
  OptionalMany
  Many
} derive(Debug, Eq)

///|
pub(all) struct PropertySpec {
  name : String
  kind : ValueKind
  cardinality : PropertyCardinality
  in_calendar : Bool
  in_event : Bool
  in_todo : Bool
  in_journal : Bool
  in_timezone : Bool
  in_observance : Bool
  in_alarm : Bool
  note : String
} derive(Debug, Eq)

///|
pub(all) enum RecurrenceFrequency {
  Secondly
  Minutely
  Hourly
  Daily
  Weekly
  Monthly
  Yearly
} derive(Debug, Eq)

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

///|
pub(all) struct DurationValue {
  negative : Bool
  weeks : Int
  days : Int
  hours : Int
  minutes : Int
  seconds : Int
} derive(Debug, Eq)

///|
pub(all) suberror DurationError {
  EmptyDuration
  MissingDurationPrefix
  BadDurationNumber(String)
  BadDurationDesignator(String)
  MixedWeekAndDateTime
  EmptyDurationBody
}

///|
pub(all) enum AlarmAction {
  Audio
  Display
  Email
  UnknownAction(String)
} derive(Debug, Eq)

///|
pub(all) struct AlarmSummary {
  action : AlarmAction
  trigger : String
  description : String?
  summary : String?
  attendee_count : Int
} derive(Debug, Eq)

///|
pub(all) struct ByDayRule {
  ordinal : Int?
  weekday : Weekday
} derive(Debug, Eq)

///|
pub(all) struct RecurrenceRule {
  frequency : RecurrenceFrequency
  interval : Int
  count : Int?
  until : String?
  wkst : Weekday?
  by_day : Array[ByDayRule]
  by_month : Array[Int]
  by_month_day : Array[Int]
  by_set_pos : Array[Int]
} derive(Debug, Eq)

///|
pub(all) suberror RecurrenceError {
  EmptyRule
  MissingFrequency
  DuplicatePart(String)
  UnknownPart(String)
  BadFrequency(String)
  BadInteger(String)
  BadWeekday(String)
  BadUntil(String)
  BadByDay(String)
  ConflictingLimit
}

///|
pub(all) struct Param {
  name : String
  values : Array[String]
} derive(Debug, Eq)

///|
pub(all) struct Property {
  name : String
  params : Array[Param]
  value : String
  line : Int
} derive(Debug, Eq)

///|
pub(all) struct Component {
  name : String
  properties : Array[Property]
  components : Array[Component]
  line : Int
} derive(Debug, Eq)

///|
pub(all) struct Calendar {
  properties : Array[Property]
  components : Array[Component]
} derive(Debug, Eq)

///|
pub(all) struct Finding {
  severity : Severity
  code : String
  message : String
  line : Int?
} derive(Debug, Eq)

///|
pub(all) struct ValidationOptions {
  require_prodid : Bool
  require_version : Bool
  max_events : Int?
  allow_unknown_components : Bool
  allow_floating_time : Bool
  require_uid : Bool
  require_dtstamp : Bool
} derive(Debug, Eq)

///|
pub(all) struct ValidationReport {
  accepted : Bool
  findings : Array[Finding]
} derive(Debug, Eq)

///|
pub(all) struct EventKey {
  uid : String
  summary : String
  starts_at : String
  ends_at : String
  rrule : String
} derive(Debug, Eq)

///|
pub(all) enum ChangeKind {
  Added
  Removed
  Changed
} derive(Debug, Eq)

///|
pub(all) struct CalendarChange {
  kind : ChangeKind
  uid : String
  before : String?
  after : String?
  breaking : Bool
} derive(Debug, Eq)

///|
pub(all) struct CalendarDiff {
  compatible : Bool
  changes : Array[CalendarChange]
} derive(Debug, Eq)

///|
pub(all) struct EventSummary {
  uid : String
  summary : String
  starts_at : String
  ends_at : String
  status : String
  location : String
  recurring : Bool
  alarm_count : Int
} derive(Debug, Eq)

///|
pub(all) struct CalendarStats {
  calendar_properties : Int
  top_level_components : Int
  total_components : Int
  total_properties : Int
  events : Int
  todos : Int
  journals : Int
  timezones : Int
  observances : Int
  alarms : Int
  recurring_components : Int
  components_with_uid : Int
  unknown_properties : Int
  unsupported_properties : Int
} derive(Debug, Eq)

///|
pub fn Severity::name(self : Severity) -> String {
  match self {
    Info => "info"
    Warn => "warn"
    Deny => "deny"
  }
}

///|
pub fn ChangeKind::name(self : ChangeKind) -> String {
  match self {
    Added => "added"
    Removed => "removed"
    Changed => "changed"
  }
}

///|
pub fn ValueKind::name(self : ValueKind) -> String {
  match self {
    Text => "TEXT"
    Date => "DATE"
    DateTime => "DATE-TIME"
    Duration => "DURATION"
    Uri => "URI"
    Integer => "INTEGER"
    Boolean => "BOOLEAN"
    Float => "FLOAT"
    CalAddress => "CAL-ADDRESS"
    Period => "PERIOD"
    Recurrence => "RECUR"
    UtcOffset => "UTC-OFFSET"
    Unknown(value) => value
  }
}

///|
pub fn ComponentRole::name(self : ComponentRole) -> String {
  match self {
    CalendarRole => "VCALENDAR"
    EventRole => "VEVENT"
    TodoRole => "VTODO"
    JournalRole => "VJOURNAL"
    TimezoneRole => "VTIMEZONE"
    ObservanceRole => "OBSERVANCE"
    AlarmRole => "VALARM"
    UnknownRole(value) => value
  }
}

///|
pub fn PropertyCardinality::name(self : PropertyCardinality) -> String {
  match self {
    RequiredOnce => "required-once"
    OptionalOnce => "optional-once"
    OptionalMany => "optional-many"
    Many => "many"
  }
}

///|
pub fn CalError::message(self : CalError) -> String {
  match self {
    EmptyInput => "input is empty"
    MissingColon(line, raw) => "line \{line} is missing ':' separator: \{raw}"
    BadPropertyName(line, raw) =>
      "line \{line} has invalid property name: \{raw}"
    BadParameter(line, raw) => "line \{line} has invalid parameter: \{raw}"
    UnexpectedEnd(name) => "END:\{name} has no matching BEGIN"
    MismatchedEnd(expected, actual) =>
      "expected END:\{expected}, got END:\{actual}"
    NestedCalendar => "nested VCALENDAR is not allowed"
    MissingCalendar => "input does not contain a VCALENDAR"
    MultipleCalendars => "input contains more than one VCALENDAR"
    PropertyOutsideCalendar(name) =>
      "property \{name} appears outside VCALENDAR"
    ComponentOutsideCalendar(name) =>
      "component \{name} appears outside VCALENDAR"
  }
}

///|
pub fn RecurrenceError::message(self : RecurrenceError) -> String {
  match self {
    EmptyRule => "RRULE is empty"
    MissingFrequency => "RRULE is missing FREQ"
    DuplicatePart(part) => "RRULE contains duplicate part: \{part}"
    UnknownPart(part) => "RRULE contains unsupported part: \{part}"
    BadFrequency(value) => "RRULE FREQ is invalid: \{value}"
    BadInteger(value) => "RRULE integer value is invalid: \{value}"
    BadWeekday(value) => "RRULE weekday value is invalid: \{value}"
    BadUntil(value) => "RRULE UNTIL value is invalid: \{value}"
    BadByDay(value) => "RRULE BYDAY value is invalid: \{value}"
    ConflictingLimit => "RRULE must not contain both COUNT and UNTIL"
  }
}

///|
pub fn DurationError::message(self : DurationError) -> String {
  match self {
    EmptyDuration => "duration is empty"
    MissingDurationPrefix => "duration must start with P or -P"
    BadDurationNumber(value) => "duration number is invalid: \{value}"
    BadDurationDesignator(value) => "duration designator is invalid: \{value}"
    MixedWeekAndDateTime => "duration must not mix W with date/time fields"
    EmptyDurationBody => "duration contains no value"
  }
}

///|
pub fn AlarmAction::name(self : AlarmAction) -> String {
  match self {
    Audio => "AUDIO"
    Display => "DISPLAY"
    Email => "EMAIL"
    UnknownAction(value) => value
  }
}

///|
pub fn RecurrenceFrequency::name(self : RecurrenceFrequency) -> String {
  match self {
    Secondly => "SECONDLY"
    Minutely => "MINUTELY"
    Hourly => "HOURLY"
    Daily => "DAILY"
    Weekly => "WEEKLY"
    Monthly => "MONTHLY"
    Yearly => "YEARLY"
  }
}

///|
pub fn Weekday::abbr(self : Weekday) -> String {
  match self {
    Monday => "MO"
    Tuesday => "TU"
    Wednesday => "WE"
    Thursday => "TH"
    Friday => "FR"
    Saturday => "SA"
    Sunday => "SU"
  }
}

///|
pub fn ValidationOptions::strict() -> ValidationOptions {
  {
    require_prodid: true,
    require_version: true,
    max_events: Some(500),
    allow_unknown_components: false,
    allow_floating_time: false,
    require_uid: true,
    require_dtstamp: true,
  }
}

///|
pub fn ValidationOptions::lenient() -> ValidationOptions {
  {
    require_prodid: false,
    require_version: false,
    max_events: None,
    allow_unknown_components: true,
    allow_floating_time: true,
    require_uid: false,
    require_dtstamp: false,
  }
}