import * as $bit_array from "../../../gleam_stdlib/gleam/bit_array.mjs";
import * as $float from "../../../gleam_stdlib/gleam/float.mjs";
import * as $int from "../../../gleam_stdlib/gleam/int.mjs";
import * as $list from "../../../gleam_stdlib/gleam/list.mjs";
import * as $order from "../../../gleam_stdlib/gleam/order.mjs";
import * as $result from "../../../gleam_stdlib/gleam/result.mjs";
import * as $string from "../../../gleam_stdlib/gleam/string.mjs";
import {
  Ok,
  Error,
  toList,
  Empty as $Empty,
  prepend as listPrepend,
  CustomType as $CustomType,
  remainderInt,
  divideFloat,
  divideInt,
  toBitArray,
  bitArraySlice,
} from "../../gleam.mjs";
import * as $calendar from "../../gleam/time/calendar.mjs";
import * as $duration from "../../gleam/time/duration.mjs";
import { system_time as get_system_time } from "../../gleam_time_ffi.mjs";

class Timestamp extends $CustomType {
  constructor(seconds, nanoseconds) {
    super();
    this.seconds = seconds;
    this.nanoseconds = nanoseconds;
  }
}

/**
 * Ensure the time is represented with `nanoseconds` being positive and less
 * than 1 second.
 *
 * This function does not change the time that the timestamp refers to, it
 * only adjusts the values used to represent the time.
 * 
 * @ignore
 */
function normalise(timestamp) {
  let multiplier = 1_000_000_000;
  let nanoseconds = remainderInt(timestamp.nanoseconds, multiplier);
  let overflow = timestamp.nanoseconds - nanoseconds;
  let seconds = timestamp.seconds + (divideInt(overflow, multiplier));
  let $ = nanoseconds >= 0;
  if ($) {
    return new Timestamp(seconds, nanoseconds);
  } else {
    return new Timestamp(seconds - 1, multiplier + nanoseconds);
  }
}

/**
 * Compare one timestamp to another, indicating whether the first is further
 * into the future (greater) or further into the past (lesser) than the
 * second.
 *
 * # Examples
 *
 * ```gleam
 * compare(from_unix_seconds(1), from_unix_seconds(2))
 * // -> order.Lt
 * ```
 */
export function compare(left, right) {
  return $order.break_tie(
    $int.compare(left.seconds, right.seconds),
    $int.compare(left.nanoseconds, right.nanoseconds),
  );
}

/**
 * Get the current system time.
 *
 * Note this time is not unique or monotonic, it could change at any time or
 * even go backwards! The exact behaviour will depend on the runtime used. See
 * the module documentation for more information.
 *
 * On Erlang this uses [`erlang:system_time/1`][1]. On JavaScript this uses
 * [`Date.now`][2].
 *
 * [1]: https://www.erlang.org/doc/apps/erts/erlang#system_time/1
 * [2]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/now
 */
export function system_time() {
  let $ = get_system_time();
  let seconds;
  let nanoseconds;
  seconds = $[0];
  nanoseconds = $[1];
  return normalise(new Timestamp(seconds, nanoseconds));
}

/**
 * Calculate the difference between two timestamps.
 *
 * This is effectively substracting the first timestamp from the second.
 *
 * # Examples
 *
 * ```gleam
 * difference(from_unix_seconds(1), from_unix_seconds(5))
 * // -> duration.seconds(4)
 * ```
 */
export function difference(left, right) {
  let seconds = $duration.seconds(right.seconds - left.seconds);
  let nanoseconds = $duration.nanoseconds(right.nanoseconds - left.nanoseconds);
  return $duration.add(seconds, nanoseconds);
}

/**
 * Add a duration to a timestamp.
 *
 * # Examples
 *
 * ```gleam
 * add(from_unix_seconds(1000), duration.seconds(5))
 * // -> from_unix_seconds(1005)
 * ```
 */
export function add(timestamp, duration) {
  let $ = $duration.to_seconds_and_nanoseconds(duration);
  let seconds;
  let nanoseconds;
  seconds = $[0];
  nanoseconds = $[1];
  let _pipe = new Timestamp(
    timestamp.seconds + seconds,
    timestamp.nanoseconds + nanoseconds,
  );
  return normalise(_pipe);
}

function pad_digit(digit, desired_length) {
  let _pipe = $int.to_string(digit);
  return $string.pad_start(_pipe, desired_length, "0");
}

function duration_to_minutes(duration) {
  return $float.round($duration.to_seconds(duration) / 60.0);
}

function modulo(n, m) {
  let $ = $int.modulo(n, m);
  if ($ instanceof Ok) {
    let n$1 = $[0];
    return n$1;
  } else {
    return 0;
  }
}

function floored_div(numerator, denominator) {
  let n = divideFloat($int.to_float(numerator), denominator);
  return $float.round($float.floor(n));
}

function to_civil(minutes) {
  let raw_day = floored_div(minutes, (60.0 * 24.0)) + 719_468;
  let _block;
  let $ = raw_day >= 0;
  if ($) {
    _block = globalThis.Math.trunc(raw_day / 146_097);
  } else {
    _block = globalThis.Math.trunc((raw_day - 146_096) / 146_097);
  }
  let era = _block;
  let day_of_era = raw_day - era * 146_097;
  let year_of_era = globalThis.Math.trunc(
    (((day_of_era - (globalThis.Math.trunc(day_of_era / 1460))) + (globalThis.Math.trunc(
      day_of_era / 36_524
    ))) - (globalThis.Math.trunc(day_of_era / 146_096))) / 365
  );
  let year = year_of_era + era * 400;
  let day_of_year = day_of_era - ((365 * year_of_era + (globalThis.Math.trunc(
    year_of_era / 4
  ))) - (globalThis.Math.trunc(year_of_era / 100)));
  let mp = globalThis.Math.trunc((5 * day_of_year + 2) / 153);
  let _block$1;
  let $1 = mp < 10;
  if ($1) {
    _block$1 = mp + 3;
  } else {
    _block$1 = mp - 9;
  }
  let month = _block$1;
  let day = (day_of_year - (globalThis.Math.trunc((153 * mp + 2) / 5))) + 1;
  let _block$2;
  let $2 = month <= 2;
  if ($2) {
    _block$2 = year + 1;
  } else {
    _block$2 = year;
  }
  let year$1 = _block$2;
  return [year$1, month, day];
}

function to_calendar_from_offset(timestamp, offset) {
  let total = timestamp.seconds + offset * 60;
  let seconds = modulo(total, 60);
  let total_minutes = floored_div(total, 60.0);
  let minutes = globalThis.Math.trunc(modulo(total, 60 * 60) / 60);
  let hours = divideInt(modulo(total, 24 * 60 * 60), 60 * 60);
  let $ = to_civil(total_minutes);
  let year;
  let month;
  let day;
  year = $[0];
  month = $[1];
  day = $[2];
  return [year, month, day, hours, minutes, seconds];
}

/**
 * Convert a `Timestamp` to calendar time, suitable for presenting to a human
 * to read.
 *
 * If you want a machine to use the time value then you should not use this
 * function and should instead keep it as a timestamp. See the documentation
 * for the `gleam/time/calendar` module for more information.
 *
 * # Examples
 *
 * ```gleam
 * timestamp.from_unix_seconds(0)
 * |> timestamp.to_calendar(calendar.utc_offset)
 * // -> #(Date(1970, January, 1), TimeOfDay(0, 0, 0, 0))
 * ```
 */
export function to_calendar(timestamp, offset) {
  let offset$1 = duration_to_minutes(offset);
  let $ = to_calendar_from_offset(timestamp, offset$1);
  let year;
  let month;
  let day;
  let hours;
  let minutes;
  let seconds;
  year = $[0];
  month = $[1];
  day = $[2];
  hours = $[3];
  minutes = $[4];
  seconds = $[5];
  let _block;
  if (month === 1) {
    _block = new $calendar.January();
  } else if (month === 2) {
    _block = new $calendar.February();
  } else if (month === 3) {
    _block = new $calendar.March();
  } else if (month === 4) {
    _block = new $calendar.April();
  } else if (month === 5) {
    _block = new $calendar.May();
  } else if (month === 6) {
    _block = new $calendar.June();
  } else if (month === 7) {
    _block = new $calendar.July();
  } else if (month === 8) {
    _block = new $calendar.August();
  } else if (month === 9) {
    _block = new $calendar.September();
  } else if (month === 10) {
    _block = new $calendar.October();
  } else if (month === 11) {
    _block = new $calendar.November();
  } else {
    _block = new $calendar.December();
  }
  let month$1 = _block;
  let nanoseconds = timestamp.nanoseconds;
  let date = new $calendar.Date(year, month$1, day);
  let time = new $calendar.TimeOfDay(hours, minutes, seconds, nanoseconds);
  return [date, time];
}

function do_remove_trailing_zeros(loop$reversed_digits) {
  while (true) {
    let reversed_digits = loop$reversed_digits;
    if (reversed_digits instanceof $Empty) {
      return reversed_digits;
    } else {
      let digit = reversed_digits.head;
      if (digit === 0) {
        let digits = reversed_digits.tail;
        loop$reversed_digits = digits;
      } else {
        let reversed_digits$1 = reversed_digits;
        return $list.reverse(reversed_digits$1);
      }
    }
  }
}

/**
 * Given a list of digits, return new list with any trailing zeros removed.
 * 
 * @ignore
 */
function remove_trailing_zeros(digits) {
  let reversed_digits = $list.reverse(digits);
  return do_remove_trailing_zeros(reversed_digits);
}

function do_get_zero_padded_digits(loop$number, loop$digits, loop$count) {
  while (true) {
    let number = loop$number;
    let digits = loop$digits;
    let count = loop$count;
    let number$1 = number;
    if ((number$1 <= 0) && (count >= 9)) {
      return digits;
    } else {
      let number$2 = number;
      if (number$2 <= 0) {
        loop$number = number$2;
        loop$digits = listPrepend(0, digits);
        loop$count = count + 1;
      } else {
        let number$3 = number;
        let digit = number$3 % 10;
        let number$4 = floored_div(number$3, 10.0);
        loop$number = number$4;
        loop$digits = listPrepend(digit, digits);
        loop$count = count + 1;
      }
    }
  }
}

/**
 * Returns the list of digits of `number`.  If the number of digits is less 
 * than 9, the result is zero-padded at the front.
 * 
 * @ignore
 */
function get_zero_padded_digits(number) {
  return do_get_zero_padded_digits(number, toList([]), 0);
}

/**
 * Converts nanoseconds into a `String` representation of fractional seconds.
 * 
 * Assumes that `nanoseconds < 1_000_000_000`, which will be true for any 
 * normalised timestamp.
 * 
 * @ignore
 */
function show_second_fraction(nanoseconds) {
  let $ = $int.compare(nanoseconds, 0);
  if ($ instanceof $order.Lt) {
    return "";
  } else if ($ instanceof $order.Eq) {
    return "";
  } else {
    let _block;
    let _pipe = nanoseconds;
    let _pipe$1 = get_zero_padded_digits(_pipe);
    let _pipe$2 = remove_trailing_zeros(_pipe$1);
    let _pipe$3 = $list.map(_pipe$2, $int.to_string);
    _block = $string.join(_pipe$3, "");
    let second_fraction_part = _block;
    return "." + second_fraction_part;
  }
}

/**
 * Convert a timestamp to a RFC 3339 formatted time string, with an offset
 * supplied as an additional argument.
 *
 * The output of this function is also ISO 8601 compatible so long as the
 * offset not negative. Offsets have at-most minute precision, so an offset
 * with higher precision will be rounded to the nearest minute.
 *
 * If you are making an API such as a HTTP JSON API you are encouraged to use
 * Unix timestamps instead of this format or ISO 8601. Unix timestamps are a
 * better choice as they don't contain offset information. Consider:
 *
 * - UTC offsets are not time zones. This does not and cannot tell us the time
 *   zone in which the date was recorded. So what are we supposed to do with
 *   this information?
 * - Users typically want dates formatted according to their local time zone.
 *   What if the provided UTC offset is different from the current user's time
 *   zone? What are we supposed to do with it then?
 * - Despite it being useless (or worse, a source of bugs), the UTC offset
 *   creates a larger payload to transfer.
 *
 * They also uses more memory than a unix timestamp. The way they are better
 * than Unix timestamp is that it is easier for a human to read them, but
 * this is a hinderance that tooling can remedy, and APIs are not primarily
 * for humans.
 *
 * # Examples
 *
 * ```gleam
 * timestamp.from_unix_seconds_and_nanoseconds(1000, 123_000_000)
 * |> to_rfc3339(calendar.utc_offset)
 * // -> "1970-01-01T00:16:40.123Z"
 * ```
 *
 * ```gleam
 * timestamp.from_unix_seconds(1000)
 * |> to_rfc3339(duration.seconds(3600))
 * // -> "1970-01-01T01:16:40+01:00"
 * ```
 */
export function to_rfc3339(timestamp, offset) {
  let offset$1 = duration_to_minutes(offset);
  let $ = to_calendar_from_offset(timestamp, offset$1);
  let years;
  let months;
  let days;
  let hours;
  let minutes;
  let seconds;
  years = $[0];
  months = $[1];
  days = $[2];
  hours = $[3];
  minutes = $[4];
  seconds = $[5];
  let offset_minutes = modulo(offset$1, 60);
  let offset_hours = $int.absolute_value(floored_div(offset$1, 60.0));
  let n2 = (_capture) => { return pad_digit(_capture, 2); };
  let n4 = (_capture) => { return pad_digit(_capture, 4); };
  let out = "";
  let out$1 = ((((out + n4(years)) + "-") + n2(months)) + "-") + n2(days);
  let out$2 = out$1 + "T";
  let out$3 = ((((out$2 + n2(hours)) + ":") + n2(minutes)) + ":") + n2(seconds);
  let out$4 = out$3 + show_second_fraction(timestamp.nanoseconds);
  let $1 = $int.compare(offset$1, 0);
  if ($1 instanceof $order.Lt) {
    return (((out$4 + "-") + n2(offset_hours)) + ":") + n2(offset_minutes);
  } else if ($1 instanceof $order.Eq) {
    return out$4 + "Z";
  } else {
    return (((out$4 + "+") + n2(offset_hours)) + ":") + n2(offset_minutes);
  }
}

function is_leap_year(year) {
  return ((year % 4) === 0) && (((year % 100) !== 0) || ((year % 400) === 0));
}

function parse_sign(bytes) {
  if (bytes.bitSize >= 8) {
    if (bytes.byteAt(0) === 43) {
      if ((bytes.bitSize - 8) % 8 === 0) {
        let remaining_bytes = bitArraySlice(bytes, 8);
        return new Ok(["+", remaining_bytes]);
      } else {
        return new Error(undefined);
      }
    } else if (bytes.byteAt(0) === 45 && (bytes.bitSize - 8) % 8 === 0) {
      let remaining_bytes = bitArraySlice(bytes, 8);
      return new Ok(["-", remaining_bytes]);
    } else {
      return new Error(undefined);
    }
  } else {
    return new Error(undefined);
  }
}

/**
 * Accept the given value from `bytes` and move past it if found.
 * 
 * @ignore
 */
function accept_byte(bytes, value) {
  if (bytes.bitSize >= 8 && (bytes.bitSize - 8) % 8 === 0) {
    let byte = bytes.byteAt(0);
    if (byte === value) {
      let remaining_bytes = bitArraySlice(bytes, 8);
      return new Ok(remaining_bytes);
    } else {
      return new Error(undefined);
    }
  } else {
    return new Error(undefined);
  }
}

function accept_empty(bytes) {
  if (bytes.bitSize === 0) {
    return new Ok(undefined);
  } else {
    return new Error(undefined);
  }
}

/**
 * Note: It is the callers responsibility to ensure the inputs are valid.
 * 
 * See https://www.tondering.dk/claus/cal/julperiod.php#formula
 * 
 * @ignore
 */
function julian_day_from_ymd(year, month, day) {
  let adjustment = globalThis.Math.trunc((14 - month) / 12);
  let adjusted_year = (year + 4800) - adjustment;
  let adjusted_month = (month + 12 * adjustment) - 3;
  return (((((day + (globalThis.Math.trunc((153 * adjusted_month + 2) / 5))) + 365 * adjusted_year) + (globalThis.Math.trunc(
    adjusted_year / 4
  ))) - (globalThis.Math.trunc(adjusted_year / 100))) + (globalThis.Math.trunc(
    adjusted_year / 400
  ))) - 32_045;
}

/**
 * Create a timestamp from a number of seconds since 00:00:00 UTC on 1 January
 * 1970.
 */
export function from_unix_seconds(seconds) {
  return new Timestamp(seconds, 0);
}

/**
 * Create a timestamp from a number of seconds and nanoseconds since 00:00:00
 * UTC on 1 January 1970.
 *
 * # JavaScript int limitations
 *
 * Remember that JavaScript can only perfectly represent ints between positive
 * and negative 9,007,199,254,740,991! If you only use the nanosecond field
 * then you will almost certainly not get the date value you want due to this
 * loss of precision. Always use seconds primarily and then use nanoseconds
 * for the final sub-second adjustment.
 */
export function from_unix_seconds_and_nanoseconds(seconds, nanoseconds) {
  let _pipe = new Timestamp(seconds, nanoseconds);
  return normalise(_pipe);
}

/**
 * Convert the timestamp to a number of seconds since 00:00:00 UTC on 1
 * January 1970.
 *
 * There may be some small loss of precision due to `Timestamp` being
 * nanosecond accurate and `Float` not being able to represent this.
 */
export function to_unix_seconds(timestamp) {
  let seconds = $int.to_float(timestamp.seconds);
  let nanoseconds = $int.to_float(timestamp.nanoseconds);
  return seconds + (nanoseconds / 1_000_000_000.0);
}

/**
 * Convert the timestamp to a number of seconds and nanoseconds since 00:00:00
 * UTC on 1 January 1970. There is no loss of precision with this conversion
 * on any target.
 */
export function to_unix_seconds_and_nanoseconds(timestamp) {
  return [timestamp.seconds, timestamp.nanoseconds];
}

const seconds_per_day = 86_400;

const seconds_per_hour = 3600;

const seconds_per_minute = 60;

function offset_to_seconds(sign, hours, minutes) {
  let abs_seconds = hours * seconds_per_hour + minutes * seconds_per_minute;
  if (sign === "-") {
    return - abs_seconds;
  } else {
    return abs_seconds;
  }
}

/**
 * `julian_seconds_from_parts(year, month, day, hours, minutes, seconds)` 
 * returns the number of Julian 
 * seconds represented by the given arguments.
 * 
 * Note: It is the callers responsibility to ensure the inputs are valid.
 * 
 * See https://www.tondering.dk/claus/cal/julperiod.php#formula
 * 
 * @ignore
 */
function julian_seconds_from_parts(year, month, day, hours, minutes, seconds) {
  let julian_day_seconds = julian_day_from_ymd(year, month, day) * seconds_per_day;
  return ((julian_day_seconds + hours * seconds_per_hour) + minutes * seconds_per_minute) + seconds;
}

const nanoseconds_per_second = 1_000_000_000;

/**
 * The `:` character as a byte
 * 
 * @ignore
 */
const byte_colon = 0x3A;

/**
 * The `-` character as a byte
 * 
 * @ignore
 */
const byte_minus = 0x2D;

/**
 * The `0` character as a byte
 * 
 * @ignore
 */
const byte_zero = 0x30;

/**
 * The `9` character as a byte
 * 
 * @ignore
 */
const byte_nine = 0x39;

function do_parse_second_fraction_as_nanoseconds(
  loop$bytes,
  loop$acc,
  loop$power
) {
  while (true) {
    let bytes = loop$bytes;
    let acc = loop$acc;
    let power = loop$power;
    let power$1 = globalThis.Math.trunc(power / 10);
    if (bytes.bitSize >= 8 && (bytes.bitSize - 8) % 8 === 0) {
      let byte = bytes.byteAt(0);
      if (((0x30 <= byte) && (byte <= 0x39)) && (power$1 < 1)) {
        let remaining_bytes = bitArraySlice(bytes, 8);
        loop$bytes = remaining_bytes;
        loop$acc = acc;
        loop$power = power$1;
      } else {
        let byte$1 = bytes.byteAt(0);
        if ((0x30 <= byte$1) && (byte$1 <= 0x39)) {
          let remaining_bytes = bitArraySlice(bytes, 8);
          let digit = byte$1 - 0x30;
          loop$bytes = remaining_bytes;
          loop$acc = acc + digit * power$1;
          loop$power = power$1;
        } else {
          return new Ok([acc, bytes]);
        }
      }
    } else {
      return new Ok([acc, bytes]);
    }
  }
}

function parse_second_fraction_as_nanoseconds(bytes) {
  if (bytes.bitSize >= 8 && bytes.byteAt(0) === 46) {
    if (bytes.bitSize >= 16 && (bytes.bitSize - 16) % 8 === 0) {
      let byte = bytes.byteAt(1);
      if ((0x30 <= byte) && (byte <= 0x39)) {
        let remaining_bytes = bitArraySlice(bytes, 16);
        return do_parse_second_fraction_as_nanoseconds(
          toBitArray([byte, remaining_bytes]),
          0,
          nanoseconds_per_second,
        );
      } else if ((bytes.bitSize - 8) % 8 === 0) {
        return new Error(undefined);
      } else {
        return new Ok([0, bytes]);
      }
    } else if ((bytes.bitSize - 8) % 8 === 0) {
      return new Error(undefined);
    } else {
      return new Ok([0, bytes]);
    }
  } else {
    return new Ok([0, bytes]);
  }
}

function do_parse_digits(loop$bytes, loop$count, loop$acc, loop$k) {
  while (true) {
    let bytes = loop$bytes;
    let count = loop$count;
    let acc = loop$acc;
    let k = loop$k;
    if (k >= count) {
      return new Ok([acc, bytes]);
    } else if (bytes.bitSize >= 8 && (bytes.bitSize - 8) % 8 === 0) {
      let byte = bytes.byteAt(0);
      if ((0x30 <= byte) && (byte <= 0x39)) {
        let remaining_bytes = bitArraySlice(bytes, 8);
        loop$bytes = remaining_bytes;
        loop$count = count;
        loop$acc = acc * 10 + (byte - 0x30);
        loop$k = k + 1;
      } else {
        return new Error(undefined);
      }
    } else {
      return new Error(undefined);
    }
  }
}

/**
 * Parse and return the given number of digits from the given bytes.
 * 
 * @ignore
 */
function parse_digits(bytes, count) {
  return do_parse_digits(bytes, count, 0, 0);
}

function parse_year(bytes) {
  return parse_digits(bytes, 4);
}

function parse_month(bytes) {
  return $result.try$(
    parse_digits(bytes, 2),
    (_use0) => {
      let month;
      let bytes$1;
      month = _use0[0];
      bytes$1 = _use0[1];
      let $ = (1 <= month) && (month <= 12);
      if ($) {
        return new Ok([month, bytes$1]);
      } else {
        return new Error(undefined);
      }
    },
  );
}

function parse_day(bytes, year, month) {
  return $result.try$(
    parse_digits(bytes, 2),
    (_use0) => {
      let day;
      let bytes$1;
      day = _use0[0];
      bytes$1 = _use0[1];
      return $result.try$(
        (() => {
          if (month === 1) {
            return new Ok(31);
          } else if (month === 3) {
            return new Ok(31);
          } else if (month === 5) {
            return new Ok(31);
          } else if (month === 7) {
            return new Ok(31);
          } else if (month === 8) {
            return new Ok(31);
          } else if (month === 10) {
            return new Ok(31);
          } else if (month === 12) {
            return new Ok(31);
          } else if (month === 4) {
            return new Ok(30);
          } else if (month === 6) {
            return new Ok(30);
          } else if (month === 9) {
            return new Ok(30);
          } else if (month === 11) {
            return new Ok(30);
          } else if (month === 2) {
            let $ = is_leap_year(year);
            if ($) {
              return new Ok(29);
            } else {
              return new Ok(28);
            }
          } else {
            return new Error(undefined);
          }
        })(),
        (max_day) => {
          let $ = (1 <= day) && (day <= max_day);
          if ($) {
            return new Ok([day, bytes$1]);
          } else {
            return new Error(undefined);
          }
        },
      );
    },
  );
}

function parse_hours(bytes) {
  return $result.try$(
    parse_digits(bytes, 2),
    (_use0) => {
      let hours;
      let bytes$1;
      hours = _use0[0];
      bytes$1 = _use0[1];
      let $ = (0 <= hours) && (hours <= 23);
      if ($) {
        return new Ok([hours, bytes$1]);
      } else {
        return new Error(undefined);
      }
    },
  );
}

function parse_minutes(bytes) {
  return $result.try$(
    parse_digits(bytes, 2),
    (_use0) => {
      let minutes;
      let bytes$1;
      minutes = _use0[0];
      bytes$1 = _use0[1];
      let $ = (0 <= minutes) && (minutes <= 59);
      if ($) {
        return new Ok([minutes, bytes$1]);
      } else {
        return new Error(undefined);
      }
    },
  );
}

function parse_seconds(bytes) {
  return $result.try$(
    parse_digits(bytes, 2),
    (_use0) => {
      let seconds;
      let bytes$1;
      seconds = _use0[0];
      bytes$1 = _use0[1];
      let $ = (0 <= seconds) && (seconds <= 60);
      if ($) {
        return new Ok([seconds, bytes$1]);
      } else {
        return new Error(undefined);
      }
    },
  );
}

function parse_numeric_offset(bytes) {
  return $result.try$(
    parse_sign(bytes),
    (_use0) => {
      let sign;
      let bytes$1;
      sign = _use0[0];
      bytes$1 = _use0[1];
      return $result.try$(
        parse_hours(bytes$1),
        (_use0) => {
          let hours;
          let bytes$2;
          hours = _use0[0];
          bytes$2 = _use0[1];
          return $result.try$(
            accept_byte(bytes$2, byte_colon),
            (bytes) => {
              return $result.try$(
                parse_minutes(bytes),
                (_use0) => {
                  let minutes;
                  let bytes$1;
                  minutes = _use0[0];
                  bytes$1 = _use0[1];
                  let offset_seconds = offset_to_seconds(sign, hours, minutes);
                  return new Ok([offset_seconds, bytes$1]);
                },
              );
            },
          );
        },
      );
    },
  );
}

function parse_offset(bytes) {
  if (bytes.bitSize >= 8) {
    if (bytes.byteAt(0) === 90) {
      if ((bytes.bitSize - 8) % 8 === 0) {
        let remaining_bytes = bitArraySlice(bytes, 8);
        return new Ok([0, remaining_bytes]);
      } else {
        return parse_numeric_offset(bytes);
      }
    } else if (bytes.byteAt(0) === 122 && (bytes.bitSize - 8) % 8 === 0) {
      let remaining_bytes = bitArraySlice(bytes, 8);
      return new Ok([0, remaining_bytes]);
    } else {
      return parse_numeric_offset(bytes);
    }
  } else {
    return parse_numeric_offset(bytes);
  }
}

/**
 * The `t` character as a byte
 * 
 * @ignore
 */
const byte_t_lowercase = 0x74;

/**
 * The `T` character as a byte
 * 
 * @ignore
 */
const byte_t_uppercase = 0x54;

/**
 * The `T` character as a byte
 * 
 * @ignore
 */
const byte_space = 0x20;

function accept_date_time_separator(bytes) {
  if (bytes.bitSize >= 8 && (bytes.bitSize - 8) % 8 === 0) {
    let byte = bytes.byteAt(0);
    if (((byte === 0x54) || (byte === 0x74)) || (byte === 0x20)) {
      let remaining_bytes = bitArraySlice(bytes, 8);
      return new Ok(remaining_bytes);
    } else {
      return new Error(undefined);
    }
  } else {
    return new Error(undefined);
  }
}

/**
 * The Julian seconds of the UNIX epoch (Julian day is 2_440_588)
 * 
 * @ignore
 */
const julian_seconds_unix_epoch = 210_866_803_200;

/**
 * Note: The caller of this function must ensure that all inputs are valid.
 * 
 * @ignore
 */
function from_date_time(
  year,
  month,
  day,
  hours,
  minutes,
  seconds,
  second_fraction_as_nanoseconds,
  offset_seconds
) {
  let julian_seconds = julian_seconds_from_parts(
    year,
    month,
    day,
    hours,
    minutes,
    seconds,
  );
  let julian_seconds_since_epoch = julian_seconds - julian_seconds_unix_epoch;
  let _pipe = new Timestamp(
    julian_seconds_since_epoch - offset_seconds,
    second_fraction_as_nanoseconds,
  );
  return normalise(_pipe);
}

/**
 * Create a `Timestamp` from a human-readable calendar time.
 *
 * # Examples
 *
 * ```gleam
 * timestamp.from_calendar(
 *   date: calendar.Date(2024, calendar.December, 25),
 *   time: calendar.TimeOfDay(12, 30, 50, 0),
 *   offset: calendar.utc_offset,
 * )
 * |> timestamp.to_rfc3339(calendar.utc_offset)
 * // -> "2024-12-25T12:30:50Z"
 * ```
 */
export function from_calendar(date, time, offset) {
  let _block;
  let $ = date.month;
  if ($ instanceof $calendar.January) {
    _block = 1;
  } else if ($ instanceof $calendar.February) {
    _block = 2;
  } else if ($ instanceof $calendar.March) {
    _block = 3;
  } else if ($ instanceof $calendar.April) {
    _block = 4;
  } else if ($ instanceof $calendar.May) {
    _block = 5;
  } else if ($ instanceof $calendar.June) {
    _block = 6;
  } else if ($ instanceof $calendar.July) {
    _block = 7;
  } else if ($ instanceof $calendar.August) {
    _block = 8;
  } else if ($ instanceof $calendar.September) {
    _block = 9;
  } else if ($ instanceof $calendar.October) {
    _block = 10;
  } else if ($ instanceof $calendar.November) {
    _block = 11;
  } else {
    _block = 12;
  }
  let month = _block;
  return from_date_time(
    date.year,
    month,
    date.day,
    time.hours,
    time.minutes,
    time.seconds,
    time.nanoseconds,
    $float.round($duration.to_seconds(offset)),
  );
}

/**
 * Parses an [RFC 3339 formatted time string][spec] into a `Timestamp`.
 *
 * [spec]: https://datatracker.ietf.org/doc/html/rfc3339#section-5.6
 * 
 * # Examples
 *
 * ```gleam
 * let assert Ok(ts) = timestamp.parse_rfc3339("1970-01-01T00:00:01Z")
 * timestamp.to_unix_seconds_and_nanoseconds(ts)
 * // -> #(1, 0)
 * ```
 * 
 * Parsing an invalid timestamp returns an error.
 * 
 * ```gleam
 * let assert Error(Nil) = timestamp.parse_rfc3339("1995-10-31")
 * ```
 *
 * ## Time zones
 *
 * It may at first seem that the RFC 3339 format includes timezone
 * information, as it can specify an offset such as `Z` or `+3`, so why does
 * this function not return calendar time with a time zone? There are multiple
 * reasons:
 *
 * - RFC 3339's timestamp format is based on calendar time, but it is
 *   unambigous, so it can be converted into epoch time when being parsed. It
 *   is always better to internally use epoch time to represent unambiguous
 *   points in time, so we perform that conversion as a convenience and to
 *   ensure that programmers with less time experience don't accidentally use
 *   a less suitable time representation.
 *
 * - RFC 3339's contains _calendar time offset_ information, not time zone
 *   information. This is enough to convert it to an unambiguous timestamp,
 *   but it is not enough information to reliably work with calendar time.
 *   Without the time zone and the time zone database it's not possible to
 *   know what time period that offset is valid for, so it cannot be used
 *   without risk of bugs.
 *
 * ## Behaviour details
 * 
 * - Follows the grammar specified in section 5.6 Internet Date/Time Format of 
 *   RFC 3339 <https://datatracker.ietf.org/doc/html/rfc3339#section-5.6>.
 * - The `T` and `Z` characters may alternatively be lower case `t` or `z`, 
 *   respectively.
 * - Full dates and full times must be separated by `T` or `t`. A space is also 
 *   permitted.
 * - Leap seconds rules are not considered.  That is, any timestamp may 
 *   specify digts `00` - `60` for the seconds.
 * - Any part of a fractional second that cannot be represented in the 
 *   nanosecond precision is tructated.  That is, for the time string, 
 *   `"1970-01-01T00:00:00.1234567899Z"`, the fractional second `.1234567899` 
 *   will be represented as `123_456_789` in the `Timestamp`.
 */
export function parse_rfc3339(input) {
  let bytes = $bit_array.from_string(input);
  return $result.try$(
    parse_year(bytes),
    (_use0) => {
      let year;
      let bytes$1;
      year = _use0[0];
      bytes$1 = _use0[1];
      return $result.try$(
        accept_byte(bytes$1, byte_minus),
        (bytes) => {
          return $result.try$(
            parse_month(bytes),
            (_use0) => {
              let month;
              let bytes$1;
              month = _use0[0];
              bytes$1 = _use0[1];
              return $result.try$(
                accept_byte(bytes$1, byte_minus),
                (bytes) => {
                  return $result.try$(
                    parse_day(bytes, year, month),
                    (_use0) => {
                      let day;
                      let bytes$1;
                      day = _use0[0];
                      bytes$1 = _use0[1];
                      return $result.try$(
                        accept_date_time_separator(bytes$1),
                        (bytes) => {
                          return $result.try$(
                            parse_hours(bytes),
                            (_use0) => {
                              let hours;
                              let bytes$1;
                              hours = _use0[0];
                              bytes$1 = _use0[1];
                              return $result.try$(
                                accept_byte(bytes$1, byte_colon),
                                (bytes) => {
                                  return $result.try$(
                                    parse_minutes(bytes),
                                    (_use0) => {
                                      let minutes;
                                      let bytes$1;
                                      minutes = _use0[0];
                                      bytes$1 = _use0[1];
                                      return $result.try$(
                                        accept_byte(bytes$1, byte_colon),
                                        (bytes) => {
                                          return $result.try$(
                                            parse_seconds(bytes),
                                            (_use0) => {
                                              let seconds;
                                              let bytes$1;
                                              seconds = _use0[0];
                                              bytes$1 = _use0[1];
                                              return $result.try$(
                                                parse_second_fraction_as_nanoseconds(
                                                  bytes$1,
                                                ),
                                                (_use0) => {
                                                  let second_fraction_as_nanoseconds;
                                                  let bytes$2;
                                                  second_fraction_as_nanoseconds = _use0[0];
                                                  bytes$2 = _use0[1];
                                                  return $result.try$(
                                                    parse_offset(bytes$2),
                                                    (_use0) => {
                                                      let offset_seconds;
                                                      let bytes$3;
                                                      offset_seconds = _use0[0];
                                                      bytes$3 = _use0[1];
                                                      return $result.try$(
                                                        accept_empty(bytes$3),
                                                        (_use0) => {
                                                          
                                                          return new Ok(
                                                            from_date_time(
                                                              year,
                                                              month,
                                                              day,
                                                              hours,
                                                              minutes,
                                                              seconds,
                                                              second_fraction_as_nanoseconds,
                                                              offset_seconds,
                                                            ),
                                                          );
                                                        },
                                                      );
                                                    },
                                                  );
                                                },
                                              );
                                            },
                                          );
                                        },
                                      );
                                    },
                                  );
                                },
                              );
                            },
                          );
                        },
                      );
                    },
                  );
                },
              );
            },
          );
        },
      );
    },
  );
}

/**
 * The epoch of Unix time, which is 00:00:00 UTC on 1 January 1970.
 */
export const unix_epoch = /* @__PURE__ */ new Timestamp(0, 0);
