///|
pub fn event_key(event : Component) -> EventKey {
  {
    uid: event.get("UID").unwrap_or(""),
    summary: event.get("SUMMARY").unwrap_or(""),
    starts_at: event.get("DTSTART").unwrap_or(""),
    ends_at: event.get("DTEND").unwrap_or(""),
    rrule: event.get("RRULE").unwrap_or(""),
  }
}

///|
pub fn diff_calendars(before : Calendar, after : Calendar) -> CalendarDiff {
  let changes : Array[CalendarChange] = []
  let before_events = before.events()
  let after_events = after.events()
  for old_event in before_events {
    let old_key = event_key(old_event)
    if old_key.uid.length() == 0 {
      continue
    }
    match find_event(after_events, old_key.uid) {
      None =>
        changes.push({
          kind: Removed,
          uid: old_key.uid,
          before: Some(event_signature(old_key)),
          after: None,
          breaking: true,
        })
      Some(new_event) => {
        let new_key = event_key(new_event)
        if event_signature(old_key) != event_signature(new_key) {
          changes.push({
            kind: Changed,
            uid: old_key.uid,
            before: Some(event_signature(old_key)),
            after: Some(event_signature(new_key)),
            breaking: false,
          })
        }
      }
    }
  }
  for new_event in after_events {
    let key = event_key(new_event)
    if key.uid.length() == 0 {
      continue
    }
    match find_event(before_events, key.uid) {
      None =>
        changes.push({
          kind: Added,
          uid: key.uid,
          before: None,
          after: Some(event_signature(key)),
          breaking: false,
        })
      Some(_) => ()
    }
  }
  { compatible: !has_breaking_calendar_change(changes), changes }
}

///|
pub fn render_diff(diff : CalendarDiff) -> String {
  let b = StringBuilder::new()
  b.write_string("# MoonCalGuard event diff\n\n")
  b.write_string("- compatible: ")
  b.write_string(diff.compatible.to_string())
  b.write_string("\n- changes: ")
  b.write_string(diff.changes.length().to_string())
  b.write_string("\n\n")
  if diff.changes.length() == 0 {
    b.write_string("No event changes detected.\n")
    return b.to_string()
  }
  for change in diff.changes {
    b.write_string("- [")
    b.write_string(change.kind.name())
    b.write_string("] ")
    b.write_string(change.uid)
    if change.breaking {
      b.write_string(" breaking")
    }
    match change.before {
      Some(before) => {
        b.write_string(" before=")
        b.write_string(before)
      }
      None => ()
    }
    match change.after {
      Some(after) => {
        b.write_string(" after=")
        b.write_string(after)
      }
      None => ()
    }
    b.write_string("\n")
  }
  b.to_string()
}

///|
fn find_event(events : Array[Component], uid : String) -> Component? {
  for event in events {
    match event.get("UID") {
      Some(id) => if id == uid { return Some(event) }
      None => ()
    }
  }
  None
}

///|
fn event_signature(key : EventKey) -> String {
  if key.rrule.length() == 0 {
    "\{key.starts_at}..\{key.ends_at} \{key.summary}"
  } else {
    "\{key.starts_at}..\{key.ends_at} \{key.summary} rrule=\{key.rrule}"
  }
}

///|
fn has_breaking_calendar_change(changes : Array[CalendarChange]) -> Bool {
  for change in changes {
    if change.breaking {
      return true
    }
  }
  false
}