import { _now, _today, _tomorrow, _nextDayOfWeek, _atTime, _startOfDay, _endOfDay, _startOfWeek, _endOfWeek, _startOfMonth, _endOfMonth, _format, _formatDate, _formatDuration, _parse } from "agency-lang/stdlib-lib/date.js"
import { _advanceTimeImpl } from "agency-lang/stdlib-lib/builtins.js"

/** @module
@summary Work with instants as epoch-millisecond numbers, and calendar dates as strings.
An instant — a specific moment in time — is a number: milliseconds since 1970,
the same value JavaScript's Date.now() gives. Do math on it directly with the
usual operators and duration literals: `now() + 2h`, `deadline - now()`. A
calendar date — a whole day, like "today" — stays a "YYYY-MM-DD" string, because
which day it is depends on the timezone. Use `format` to turn an instant into a
readable string, `formatDate` for its calendar date, and `parse` to read an ISO
string back into an instant.

```ts
import { now, atTime, nextDayOfWeek, format } from "std::date"
import { createEvent } from "std::calendar"

node main() {
  // 3pm next Monday Pacific for one hour
  const tz = "America/Los_Angeles"
  const start = atTime(nextDayOfWeek("monday", tz), "15:00", tz)
  createEvent(summary: "Dentist", start: format(start, tz), end: format(start + 1h, tz))
}
```
*/

/** A day of the week, lowercase. */
export type DayOfWeek = "sunday" | "monday" | "tuesday" | "wednesday" | "thursday" | "friday" | "saturday"

/**
Use `format(now(), timezone)` to display the instant in a timezone.
The numeric instant itself identifies the same moment in every timezone.
*/
export def now(): number {
  """
  Get the current time as milliseconds since the Unix epoch.
  """
  return _now()
}

/** Test helper (import test { _advanceTime }): advance the fake clock so a
time guard trips without spending real time. Only works under a test case with
"fakeClock": true; throws otherwise. */
def _advanceTime(ms: number) {
  """
  Test-only: move the fake clock forward `ms` milliseconds, tripping any time
  guard whose budget is now exceeded.

  @param ms - How many milliseconds of fake time to advance.
  """
  _advanceTimeImpl(ms)
}

export def today(timezone: string = ""): string {
  """
  Get today's date as a YYYY-MM-DD string (e.g. "2026-05-05").

  @param timezone - IANA timezone name (defaults to the local timezone)
  """
  return _today(timezone)
}

export def tomorrow(timezone: string = ""): string {
  """
  Get tomorrow's date as a YYYY-MM-DD string (e.g. "2026-05-06").

  @param timezone - IANA timezone name (defaults to the local timezone)
  """
  return _tomorrow(timezone)
}

export def nextDayOfWeek(day: DayOfWeek, timezone: string = ""): string {
  """
  Get the next occurrence of a weekday as a YYYY-MM-DD date.

  @param timezone - IANA timezone name, or empty for the local timezone.
  """
  return _nextDayOfWeek(day, timezone)
}

/**
Use `atTime("2026-05-05", "09:00", "America/New_York")` to schedule a
local wall-clock time. Invalid dates and times throw an error.
*/
export def atTime(date: string, time: string, timezone: string = ""): number {
  """
  Convert a calendar date and local time to epoch milliseconds.

  @param date - Calendar date in YYYY-MM-DD form.
  @param time - Wall-clock time in HH:MM or HH:MM:SS form.
  @param timezone - IANA timezone name, or empty for the local timezone.
  """
  return _atTime(date, time, timezone)
}

export def startOfDay(instant?: number, timezone: string = ""): number {
  """
  Get midnight of the day containing the instant, as epoch milliseconds.

  @param instant - Epoch milliseconds within the day, or omit for now.
  @param timezone - IANA timezone name, or empty for the local timezone.
  """
  const at = instant ?? now()
  return _startOfDay(at, timezone)
}

export def endOfDay(instant?: number, timezone: string = ""): number {
  """
  Get the last millisecond of the day containing the instant.

  @param instant - Epoch milliseconds within the day, or omit for now.
  @param timezone - IANA timezone name, or empty for the local timezone.
  """
  const at = instant ?? now()
  return _endOfDay(at, timezone)
}

export def startOfWeek(instant?: number, timezone: string = ""): number {
  """
  Get midnight on Sunday of the week containing the instant, as epoch milliseconds.

  @param instant - Epoch milliseconds within the week, or omit for now.
  @param timezone - IANA timezone name, or empty for the local timezone.
  """
  const at = instant ?? now()
  return _startOfWeek(at, timezone)
}

export def endOfWeek(instant?: number, timezone: string = ""): number {
  """
  Get the last millisecond of Saturday of the week containing the instant.

  @param instant - Epoch milliseconds within the week, or omit for now.
  @param timezone - IANA timezone name, or empty for the local timezone.
  """
  const at = instant ?? now()
  return _endOfWeek(at, timezone)
}

export def startOfMonth(instant?: number, timezone: string = ""): number {
  """
  Get midnight on the first day of the month containing the instant, as epoch milliseconds.

  @param instant - Epoch milliseconds within the month, or omit for now.
  @param timezone - IANA timezone name, or empty for the local timezone.
  """
  const at = instant ?? now()
  return _startOfMonth(at, timezone)
}

export def endOfMonth(instant?: number, timezone: string = ""): number {
  """
  Get the last millisecond of the month containing the instant.

  @param instant - Epoch milliseconds within the month, or omit for now.
  @param timezone - IANA timezone name, or empty for the local timezone.
  """
  const at = instant ?? now()
  return _endOfMonth(at, timezone)
}

export def format(ms: number, timezone: string = ""): string {
  """
  Format an instant (epoch milliseconds) as an ISO 8601 string with
  milliseconds and offset, e.g. "2026-05-05T10:30:00.123-07:00".

  @param ms - The instant to format
  @param timezone - IANA timezone name (defaults to local)
  """
  return _format(ms, timezone)
}

export def formatDate(ms: number, timezone: string = ""): string {
  """
  Format an instant as the "YYYY-MM-DD" calendar date it falls on in a timezone.

  @param ms - The instant to format
  @param timezone - IANA timezone name (defaults to local)
  """
  return _formatDate(ms, timezone)
}

/**
Parsing follows JavaScript Date rules, so inputs such as `"2026"` and
`"2026-05"` are also accepted. Invalid input throws an error.
*/
export def parse(iso: string): number {
  """
  Parse a datetime string into epoch milliseconds.
  """
  return _parse(iso)
}

/**
Output uses whole seconds, with weeks as the largest unit.
*/
export def formatDuration(ms: number): string {
  """
  Format a duration in milliseconds as text such as "5m 32s".
  """
  return _formatDuration(ms)
}

export def elapsedTime(since: number): string {
  """
  Format the time since an earlier instant as a duration such as "5m 32s".

  @param since - Starting time in epoch milliseconds, such as a value from now().
  """
  return formatDuration(now() - since)
}
