package ch_02

import cats.effect.*
import utils.*
import scala.concurrent.duration.*
import scala.io.StdIn

// unrestricted cancellation may lead to inconsistent state
def faultyPaymentSystem[A](core: IO[A]): IO[Unit] =
  for
    fib <- core.delayBy(1.second).onCancel(IO.pure("shouldn't be possible").void).start
    _   <- IO.sleep(500.millis) >> fib.cancel
    _   <- fib.join
  yield ()

// Poll[F] => F[_] marks sections within the returned effect which can be canceled
def uncancelledPaymentSystem[A](core: IO[A]): IO[Unit] =
  for
    fib <- core.delayBy(1.second).uncancelable.start
    _   <- IO.sleep(500.millis) >> fib.cancel
    _   <- fib.join
  yield ()

def inputPassword: IO[String]          = IO(StdIn.readLine()).delayBy(2.seconds)
def hashPassword(pwd: String): IO[Int] = IO.pure(pwd).delayBy(2.seconds).map(util.hashing.MurmurHash3.stringHash)

def authFlow(dbHash: IO[Int]): IO[Boolean] = IO.uncancelable { (poll: Poll[IO]) =>
  for
    pwd    <- poll(inputPassword).onCancel(IO("inputPassword canceled").void)
    hash   <- hashPassword(pwd)     // no longer cancelable
    isSame <- dbHash.map(_ == hash) // no longer cancelable
  yield isSame
}

// IO.canceled is ignored because it's not explicitly marked by Poll[F] (think in terms of the call-stack)
def overrideCancellation: IO[Unit] = IO.uncancelable(_ => IO.canceled >> IO.unit)

// the outer Poll[F] mask overrides the inner Poll[F] mask so the authFlow effect is entirely uncancelable
def overrideAuthFlowCancellation(dbHash: IO[Int]): IO[OutcomeIO[Boolean]] =
  for
    authFib <- IO.uncancelable(_ => authFlow(dbHash)).start // while IO.uncancelable(poll => poll(authFlow)) == authFlow
    _       <- IO.sleep(1.second) >> authFib.cancel
    res     <- authFib.join
  yield res

def cancellationSignals: IO[Unit] =
  val program = IO.uncancelable { poll =>
    poll(IO.pure("first cancelable region").inspect >> IO.sleep(1.second)) >>
      IO.pure("uncancelable region").inspect >> IO.sleep(1.second) >>
      poll(IO.pure("second cancelable region").inspect >> IO.sleep(1.second))
  }

  program.onCancel(IO.pure("successful cancellation").inspect.void)
  // [0 - 1000]    - cancellation signal is received within the first cancelable region, 'program' gets canceled
  // [1000 - 2000] - cancellation signal is received within an uncancelable region, 'program' continues to execute until
  //                 it enters the second cancelable region where it gets canceled
  // [2000 - 3000] - cancellation signal is received within the second cancelable region, 'program' gets canceled
  for
    fib <- program.start
    _   <- IO.pure("cancellation attempt").inspect.delayBy(1500.millis) >> fib.cancel
    _   <- fib.join
  yield ()