///|
priv enum LocalKind {
  Iana(Location)
  Fixed(FixedOffset)
  Posix(PosixTz)
}

///|
/// The host's configured local time zone. On `native` it is resolved from
/// `$TZ` or, when unset, `/etc/localtime`, with zone names also looked up in
/// the zoneinfo directories of `Location::load_system`; on `js` from the
/// runtime's `Intl` zone name; on `wasm` and `wasm-gc` from `$TZ` alone, since
/// neither has host-provided file access.
pub struct Local {
  priv kind : LocalKind
}

///|
/// Resolves `tz_env` (the `TZ` environment variable) into a `Local`, reading
/// files through `read_file` (a path to its raw bytes, `None` if unreadable).
/// One leading `:` of `TZ` is ignored. Then an empty `TZ` (including a lone
/// `:`) means UTC; a `TZ` starting with `/` is the path of a TZif file; any
/// other value is looked up as an IANA zone name and, failing that, parsed as
/// a bare POSIX rule (e.g. `JST-9`). A zone name is searched in
/// `zoneinfo_dirs` before the embedded zones. `None` if the value is none of
/// these or the file cannot be read as TZif. When `TZ` is unset entirely,
/// `/etc/localtime` is read instead.
fn Local::resolve(
  tz_env : String?,
  read_file : (String) -> Bytes?,
  zoneinfo_dirs? : Array[String] = [],
) -> Local? {
  let from_file = path => {
    read_file(path)
    .bind(bytes => Location::from_tzif_bytes(bytes))
    .map(loc => Local::{ kind: Iana(loc), })
  }
  match tz_env {
    None => from_file("/etc/localtime")
    Some(value) => {
      let name = match value {
        [':', .. rest] => rest.to_owned()
        _ => value
      }
      match name {
        "" => Some(Local::{ kind: Fixed(FixedOffset::east(0).unwrap()), })
        ['/', ..] => from_file(name)
        _ =>
          match Location::load_from_dirs(name, zoneinfo_dirs, read_file) {
            Some(loc) => Some(Local::{ kind: Iana(loc), })
            None => parse_posix_tz(name).map(p => Local::{ kind: Posix(p), })
          }
      }
    }
  }
}

///|
pub impl TimeZone for Local with fn offset_from_utc(self, utc) {
  match self.kind {
    Iana(loc) => loc.offset_from_utc(utc)
    Fixed(off) => off.offset_from_utc(utc)
    Posix(p) => p.offset_from_utc(utc)
  }
}

///|
pub impl TimeZone for Local with fn offset_from_local(self, naive_local) {
  match self.kind {
    Iana(loc) => loc.offset_from_local(naive_local)
    Fixed(off) => off.offset_from_local(naive_local)
    Posix(p) => p.offset_from_local(naive_local)
  }
}

///|
pub impl TimeZone for Local with fn zone_name(self, utc) {
  match self.kind {
    Iana(loc) => loc.zone_name(utc)
    Fixed(off) => off.zone_name(utc)
    Posix(p) => p.zone_name(utc)
  }
}

///|
pub impl TimeZone for Local with fn is_dst(self, utc) {
  match self.kind {
    Iana(loc) => loc.is_dst(utc)
    Fixed(off) => off.is_dst(utc)
    Posix(p) => p.is_dst(utc)
  }
}

///|
pub impl TimeZone for Local with fn transition_bounds(self, utc) {
  match self.kind {
    Iana(loc) => loc.transition_bounds(utc)
    Fixed(off) => off.transition_bounds(utc)
    Posix(p) => p.transition_bounds(utc)
  }
}

///|
pub impl TimeZone for Local with fn offset_from_abbreviation(
  self,
  abbreviation,
  near,
) {
  match self.kind {
    Iana(loc) => loc.offset_from_abbreviation(abbreviation, near)
    Fixed(off) => off.offset_from_abbreviation(abbreviation, near)
    Posix(p) => p.offset_from_abbreviation(abbreviation, near)
  }
}

///|
pub extend Local with TimeZone::{
  offset_from_utc,
  offset_from_local,
  zone_name,
  is_dst,
  transition_bounds,
  offset_from_abbreviation,
}

///|
/// The current instant expressed in the host's local time zone, or `None`
/// if that zone could not be determined (see `Local::new`). The counterpart
/// of `Utc::now`, and like it reads the host clock; the `None` is about the
/// zone, not the clock. The precision is at most one millisecond, as for
/// `Utc::now`.
pub fn Local::now() -> DateTime[Local]? {
  Local::new().map(zone => Utc::now().with_timezone(zone))
}