package docspell.backend.ops

import cats.effect.{Effect, Resource}
import cats.implicits._

import docspell.common.{AccountId, Ident}
import docspell.store.records.{RTag, RTagItem}
import docspell.store.{AddResult, Store}

trait OTag[F[_]] {

  def findAll(account: AccountId, nameQuery: Option[String]): F[Vector[RTag]]

  def add(s: RTag): F[AddResult]

  def update(s: RTag): F[AddResult]

  def delete(id: Ident, collective: Ident): F[AddResult]

  /** Load all tags given their ids. Ids that are not available are ignored.
    */
  def loadAll(ids: List[Ident]): F[Vector[RTag]]
}

object OTag {

  def apply[F[_]: Effect](store: Store[F]): Resource[F, OTag[F]] =
    Resource.pure[F, OTag[F]](new OTag[F] {
      def findAll(account: AccountId, nameQuery: Option[String]): F[Vector[RTag]] =
        store.transact(RTag.findAll(account.collective, nameQuery, _.name))

      def add(t: RTag): F[AddResult] = {
        def insert = RTag.insert(t)
        def exists = RTag.existsByName(t)

        val msg = s"A tag '${t.name}' already exists"
        store.add(insert, exists).map(_.fold(identity, _.withMsg(msg), identity))
      }

      def update(t: RTag): F[AddResult] = {
        def insert = RTag.update(t)
        def exists = RTag.existsByName(t)

        val msg = s"A tag '${t.name}' already exists"
        store.add(insert, exists).map(_.fold(identity, _.withMsg(msg), identity))
      }

      def delete(id: Ident, collective: Ident): F[AddResult] = {
        val io = for {
          optTag <- RTag.findByIdAndCollective(id, collective)
          n0     <- optTag.traverse(t => RTagItem.deleteTag(t.tagId))
          n1     <- optTag.traverse(t => RTag.delete(t.tagId, collective))
        } yield n0.getOrElse(0) + n1.getOrElse(0)
        store.transact(io).attempt.map(AddResult.fromUpdate)
      }

      def loadAll(ids: List[Ident]): F[Vector[RTag]] =
        if (ids.isEmpty) Vector.empty.pure[F]
        else store.transact(RTag.findAllById(ids))
    })
}