package gs.std.v0.effect

import cats.effect.Sync
import cats.syntax.all.*
import java.time.Clock
import java.time.Instant
import java.time.LocalDate
import java.time.OffsetDateTime
import java.time.ZonedDateTime
import java.time.ZoneId

/** Implementation of [[DateTimeProvider]] based on a specific
  * `java.time.Clock`. All dates and times are calculated using this clock.
  *
  * @param clock
  *   The clock used to calculate dates and times.
  */
final class ClockDateTimeProvider[F[_]: Sync](
  val clock: Clock
) extends DateTimeProvider[F]:

  /** @inheritDocs
    */
  override def timestamp(): F[Instant] =
    Sync[F].delay(Instant.now(clock))

  /** @inheritDocs
    */
  override def nowInstant(): F[Instant] =
    Sync[F].delay(Instant.now(clock))

  /** @inheritDocs
    */
  override def nowZoned(): F[ZonedDateTime] =
    Sync[F].delay(ZonedDateTime.now(clock))

  /** @inheritDocs
    */
  override def nowOffset(): F[OffsetDateTime] =
    Sync[F].delay(OffsetDateTime.now(clock))

  /** @inheritDocs
    */
  override def today(): F[LocalDate] =
    Sync[F].delay(LocalDate.now(clock))

  /** @inheritDocs
    */
  override def yesterday(): F[LocalDate] =
    today().map(_.minusDays(1L))

  /** @inheritDocs
    */
  override def tomorrow(): F[LocalDate] =
    today().map(_.plusDays(1L))

object ClockDateTimeProvider:

  /** Instantiate a new [[ClockDateTimeProvider]].
    *
    * @param clock
    *   The clock to use for all date/time calculations.
    * @return
    *   The new [[DateTimeProvider]] instance.
    */
  def apply[F[_]: Sync](clock: Clock): DateTimeProvider[F] =
    new ClockDateTimeProvider[F](clock)

  /** @return
    *   A new [[ClockDateTimeProvider]] based on the system's default zone using
    *   the system's clock.
    */
  def system[F[_]: Sync]: DateTimeProvider[F] =
    new ClockDateTimeProvider[F](Clock.systemDefaultZone())

  /** Use a system clock aligned to the given zone.
    *
    * @param zoneId
    *   The identifier of the zone to target.
    * @return
    *   A new [[ClockDateTimeProvider]] based on the specified zone using the
    *   system's clock.
    */
  def forZone[F[_]: Sync](zoneId: ZoneId): DateTimeProvider[F] =
    new ClockDateTimeProvider[F](Clock.system(zoneId))

end ClockDateTimeProvider
