package gs.std.v0.io

import cats.effect.Resource
import cats.effect.Sync
import java.io.InputStream
import java.io.OutputStream
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.nio.file.OpenOption
import java.nio.file.Path
import java.nio.file.StandardOpenOption
import scala.jdk.CollectionConverters.*

/** Common operations on files.
  *
  * Includes wrappers around some `java.nio.file.Files` functionality.
  *
  * For streaming operations, just use the `fs2.io` library.
  */
object Files:

  def readFileAsString[F[_]: Sync](
    path: Path,
    charset: Charset = StandardCharsets.UTF_8
  ): F[String] =
    Sync[F].delay(java.nio.file.Files.readString(path, charset))

  def readFileAsBytes[F[_]: Sync](path: Path): F[Array[Byte]] =
    Sync[F].delay(java.nio.file.Files.readAllBytes(path))

  def readFileAsLines[F[_]: Sync](
    path: Path,
    charset: Charset = StandardCharsets.UTF_8
  ): F[List[String]] =
    Sync[F].delay(
      java.nio.file.Files.readAllLines(path, charset).asScala.toList
    )

  def overwriteFileWithString[F[_]: Sync](
    path: Path,
    data: String
  ): F[Unit] =
    Sync[F].delay(
      java.nio.file.Files.writeString(
        path,
        data,
        StandardOpenOption.WRITE,
        StandardOpenOption.TRUNCATE_EXISTING
      )
    )

  def overwriteFileWithBytes[F[_]: Sync](
    path: Path,
    data: Array[Byte]
  ): F[Unit] =
    Sync[F].delay(
      java.nio.file.Files.write(
        path,
        data,
        StandardOpenOption.WRITE,
        StandardOpenOption.TRUNCATE_EXISTING
      )
    )

  def appendFileWithString[F[_]: Sync](
    path: Path,
    data: String
  ): F[Unit] =
    Sync[F].delay(
      java.nio.file.Files.writeString(
        path,
        data,
        StandardOpenOption.WRITE,
        StandardOpenOption.APPEND
      )
    )

  def appendFileWithBytes[F[_]: Sync](
    path: Path,
    data: Array[Byte]
  ): F[Unit] =
    Sync[F].delay(
      java.nio.file.Files
        .write(path, data, StandardOpenOption.WRITE, StandardOpenOption.APPEND)
    )

  def isRegularFile[F[_]: Sync](path: Path): F[Boolean] =
    Sync[F].delay(java.nio.file.Files.isRegularFile(path))

  def isReadable[F[_]: Sync](path: Path): F[Boolean] =
    Sync[F].delay(java.nio.file.Files.isReadable(path))

  def isWritable[F[_]: Sync](path: Path): F[Boolean] =
    Sync[F].delay(java.nio.file.Files.isWritable(path))

  def isDirectory[F[_]: Sync](path: Path): F[Boolean] =
    Sync[F].delay(java.nio.file.Files.isDirectory(path))

  def isExecutable[F[_]: Sync](path: Path): F[Boolean] =
    Sync[F].delay(java.nio.file.Files.isExecutable(path))

  def openInputStream[F[_]: Sync](
    path: Path,
    openOptions: OpenOption*
  ): Resource[F, InputStream] =
    Resource.make(
      Sync[F].delay(java.nio.file.Files.newInputStream(path, openOptions*))
    )(inputStream => Sync[F].delay(inputStream.close()))

  def openOutputStream[F[_]: Sync](
    path: Path,
    openOptions: OpenOption*
  ): Resource[F, OutputStream] =
    Resource.make(
      Sync[F].delay(java.nio.file.Files.newOutputStream(path, openOptions*))
    )(outputStream => Sync[F].delay(outputStream.close()))

end Files
