package mdr.pdb.app package state import zio.* import com.raquo.airstream.core.{EventStream, Signal} import com.raquo.airstream.state.{Val, Var} import mdr.pdb.{UserInfo, OsobniCislo} import com.raquo.airstream.core.Observer import scala.scalajs.js import scala.scalajs.js.JSON import zio.json.{*, given} import com.raquo.airstream.eventbus.EventBus import com.raquo.airstream.ownership.Owner import com.raquo.waypoint.Router import mdr.pdb.Parameter import mdr.pdb.ParameterCriteria import mdr.pdb.UserFunction import mdr.pdb.UserContract import fiftyforms.services.files.File import sttp.tapir.DecodeResult import com.raquo.airstream.ownership.OneTimeOwner import scala.annotation.unused import com.raquo.airstream.ownership.Subscription trait AppState extends components.AppPage.AppState with connectors.DirectoryPageConnector.AppState with connectors.DetailPageConnector.AppState with connectors.DetailParametruPageConnector.AppState with connectors.DetailKriteriaPageConnector.AppState with pages.detail.UpravDukaz.State: def online: Signal[Boolean] def users: EventStream[List[UserInfo]] def details: EventStream[UserInfo] def parameters: EventStream[List[Parameter]] def actionBus: Observer[Action] object AppStateLive: def layer: URLayer[ZEnv & AppConfig & Api & Router[Page], AppState] = { (ZLayer.fromZIO(ZIO.runtime[ZEnv]) ++ ZIOOwner.layer) >>> ( ( appConfig: AppConfig, api: Api, router: Router[Page], runtime: Runtime[ZEnv], owner: Owner ) => AppStateLive(appConfig, api, router, runtime)(using owner) ).toLayer[AppState] } class AppStateLive( appConfig: AppConfig, api: Api, router: Router[Page], runtime: Runtime[ZEnv] )(using owner: Owner ) extends AppState: given JsonDecoder[OsobniCislo] = JsonDecoder.string.map(OsobniCislo.apply) given JsonDecoder[UserFunction] = DeriveJsonDecoder.gen given JsonDecoder[UserContract] = DeriveJsonDecoder.gen given JsonDecoder[UserInfo] = DeriveJsonDecoder.gen given JsonDecoder[ParameterCriteria] = DeriveJsonDecoder.gen given JsonDecoder[Parameter] = DeriveJsonDecoder.gen private val actions = EventBus[Action]() private val (parametersStream, pushParameters) = EventStream.withCallback[List[Parameter]] private val (usersStream, pushUsers) = EventStream.withCallback[List[UserInfo]] private val (detailsStream, pushDetails) = EventStream.withCallback[UserInfo] private val (filesStream, pushFiles) = EventStream.withCallback[List[File]] private val isOnline = Var(true) private val mockData: List[UserInfo] = mockUsers .asInstanceOf[js.Dictionary[js.Object]] .values // TODO: is there a more efficient way to parse from JS object directly? .map(JSON.stringify(_).fromJson[UserInfo]) .collect { case Right(u) => u } .toList private val mockParameters: List[Parameter] = pdbParams .asInstanceOf[js.Dictionary[js.Object]] .values .map(o => JSON.stringify(o).fromJson[Parameter]) .collect { case Right(p) => p } .toList private def scheduleOnlineCheck(): Unit = appConfig.onlineCheckMs.foreach(d => actions.writer.delay(d).onNext(CheckOnlineState) ) // TODO: Extract to separate event handler private val handler: Action => Task[Unit] = case CheckOnlineState => for o <- api.isAlive() _ <- Task.attempt { isOnline.set(o) scheduleOnlineCheck() } yield () case FetchDirectory => for users <- api.listUsers() _ <- Task.attempt(pushUsers(users)) yield () case FetchUserDetails(osc) => Task.attempt { mockData.find(_.personalNumber == osc).foreach { o => pushDetails(o) router.replaceState(Page.Detail(o)) } } case FetchParameters(osc) => Task.attempt(pushParameters(mockParameters)) case FetchParameter(osc, paramId) => Task.attempt { for o <- mockData.find(_.personalNumber == osc) p <- mockParameters.find(_.id == paramId) do pushDetails(o) pushParameters(mockParameters) router.replaceState(Page.DetailParametru(o, p)) } case FetchParameterCriteria(osc, paramId, critId, page) => Task.attempt { for o <- mockData.find(_.personalNumber == osc) p <- mockParameters.find(_.id == paramId) c <- p.criteria.find(_.id == critId) do pushDetails(o) pushParameters(mockParameters) router.replaceState(page(o, p, c)) } case NavigateTo(page) => Task.attempt { router.pushState(page) } case FetchAvailableFiles(osc) => Task.attempt { pushFiles( List( File("https://tc163.cmi.cz/here", "Example file") ) ) } actions.events.foreach(action => runtime.unsafeRunAsync(handler(action))) scheduleOnlineCheck() override def online: Signal[Boolean] = isOnline.signal override def users: EventStream[List[UserInfo]] = usersStream.debugWithName("users") override def details: EventStream[UserInfo] = detailsStream.debugWithName("details") override def parameters: EventStream[List[Parameter]] = parametersStream.debugWithName("parameters") override def availableFiles: EventStream[List[File]] = filesStream.debugWithName("available files") override def actionBus: Observer[Action] = actions.writer.debugWithName("actions writer")