package external
package library

import cats.arrow.FunctionK
import cats.effect.{Concurrent, ContextShift, IO}
import external.library.IoAdapt.-->
import zio.{Task, ZIO}

import scala.concurrent.Future

/**
  * Models a natural transformation between the Functors `F[_]` and `G[_]`.
  */
sealed trait IoAdapt[F[_], G[_]] {

  /**
    * Gives the Natural Transformation from `F` to `G` for all the types `A` where `F` is called by name
    */
  def apply[A]: (=>F[A]) => G[A]

  def functionK: FunctionK[F, G] =
    λ[FunctionK[F, G]](apply(_))
}

private[library] sealed trait IoAdaptInstances {
  implicit def catsIoToZioTask(implicit cc: Concurrent[Task]): IO --> Task =
    new IoAdapt[IO, Task] {
      def apply[A]: (=>IO[A]) => Task[A] =
        io => cc.liftIO(io)
    }

  implicit val futureToZioTask: Future --> Task =
    new IoAdapt[Future, Task] {
      def apply[A]: (=>Future[A]) => Task[A] =
        ft => ZIO.fromFuture(ec => ft.map(identity)(ec))
    }

  implicit def futureToIo(implicit cs: ContextShift[IO]): Future --> IO =
    new IoAdapt[Future, IO] {
      def apply[A]: (=>Future[A]) => IO[A] =
        IO.fromFuture[A] _ compose IO.delay
    }
}

object IoAdapt extends IoAdaptInstances {
  type -->[F[_], G[_]] = IoAdapt[F, G]
}