(* YOCaml a static blog generator.
   Copyright (C) 2024 The Funkyworkers and The YOCaml's developers

   This program is free software: you can redistribute it and/or modify
   it under the terms of the GNU General Public License as published by
   the Free Software Foundation, either version 3 of the License, or
   (at your option) any later version.

   This program is distributed in the hope that it will be useful,
   but WITHOUT ANY WARRANTY; without even the implied warranty of
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
   GNU General Public License for more details.

   You should have received a copy of the GNU General Public License
   along with this program.  If not, see <https://www.gnu.org/licenses/>. *)

(** Actions consume {!module:Pipeline} to produce artifacts. This is the entry
    point for a {b construction rule}. In general, a chain of actions maitizes a
    cache and is used in this way:

    {eof@ocaml skip[
      let open Eff.Infix in
      restore_cache ~on path_of_cache
      >>= action_a
      >>= action_b
      >>= action_c
      >>= store_cache ~on path_of_cache
    ]eof} *)

type t = Cache.t -> Cache.t Eff.t
(** As it is necessary to maintain the cache during the various artifact
    production phases, an action is a function that takes a cache and returns
    the modified cache, wrapped in an effect. *)

val restore_cache : ?on:Eff.filesystem -> Path.t -> Cache.t Eff.t
(** [restore_cache ?on path] Reads or initiates the cache in a given [path]. *)

val store_cache : ?on:Eff.filesystem -> Path.t -> Cache.t -> unit Eff.t
(** [store_cache ?on path cache] saves the [cache] in a given [path]. *)

val write_dynamic_file : Path.t -> (unit, string * Deps.t) Task.t -> t
(** [write_dynamic_file target task cache] Writes [target] file with content
    generated by [task] if necessary. Returns the modified cache once the action
    has been performed.

    The task passed as an argument returns the contents of the file to be built
    and the dynamic dependencies produced by the task (which will be cached). *)

val write_static_file : Path.t -> (unit, string) Task.t -> t
(** [write_static_file target task cache] is exactly
    [write_dynamic_file target task cache] but the action assume that no dynamic
    dependencies are involved in the task. It is like using
    [ t ||> no_dynamic_deps ] at the end of the task pipeline. *)

val exec_cmd : ?is_success:(int -> bool) -> (Cmd.value -> Cmd.t) -> Path.t -> t
(** [exec_cmd ?is_success cmd target] produces a [target] performing
    [cmd target] (the argument of the given callback, [cmd] is prefilled with
    the target as a not watched path). When [is_success] is provided, it is
    called with the exit code to determine whether it indicates success or
    failure. Without [is_success], success requires the process to return an
    exit code of 0. *)

val perform :
     Path.t
  -> ('a, 'b) Task.t
  -> when_creation:(int -> Path.t -> ('a -> 'b Eff.t) -> t)
  -> when_update:(int -> Path.t -> ('a -> 'b Eff.t) -> t)
  -> t
(** [perform target task ~when_creation ~when_update cache] is a generic task
    performer. (It executes [when_creation] if the target has to be created, and
    [need_update] if the target must be updated). *)

val copy_file : ?new_name:Path.fragment -> into:Path.t -> Path.t -> t
(** [copy_file ?new_name ~into:target source cache] Copies the [source] file to
    the [target] directory (potentially giving it a new name), taking account of
    dependencies. The copy is obviously static. *)

val copy_directory : ?new_name:Path.fragment -> into:Path.t -> Path.t -> t
(** [copy_directory ?new_name ~into:target source cache] Copies recursively the
    [source] file to the [target] directory (potentially giving it a new name),
    taking account of dependencies. The copy is obviously static.

    {b Warning} The use of this action is relatively optimistic. If only one
    child has been modified, the entire copy will be replayed. *)

val batch :
     ?only:[ `Files | `Directories | `Both ]
  -> ?where:(Path.t -> bool)
  -> Path.t
  -> (Path.t -> t)
  -> t
(** [batch ?only ?where path action cache] Executes the given action on all
    child files of the given path. The cache is passed from call to call. *)

val fold :
     ?only:[ `Files | `Directories | `Both ]
  -> ?where:(Path.t -> bool)
  -> state:'a
  -> Path.t
  -> (Path.t -> 'a -> Cache.t -> (Cache.t * 'a) Eff.t)
  -> Cache.t
  -> (Cache.t * 'a) Eff.t
(** [fold ?only ?where ~state path action cache] Executes the given action on
    all child files of the given path. The cache is passed from call to call and
    instead of {!val:batch}, you can maintain your own additional state. *)

val batch_list : 'a list -> ('a -> t) -> t
(** [batch_list list action cache] Executes the given action on all element of
    the given list. The cache is passed from call to call. *)

val fold_list :
     state:'a
  -> 'b list
  -> ('b -> 'a -> Cache.t -> (Cache.t * 'a) Eff.t)
  -> Cache.t
  -> (Cache.t * 'a) Eff.t
(** [fold_list ~state list action cache] Executes the given action on all
    element of the given list. The cache is passed from call to call and instead
    of {!val:batch_list}, you can maintain your own additional state. *)

(** {1 Helpers for dealing with static and dynamic dependencies}

    The API can change considerably when processing tasks with or without
    dynamic dependencies, so we are exposing two modules to simplify this
    processing. *)

module Static : sig
  (** Utilities for dealing with tasks without dynamic dependencies. *)

  val write_file : Path.t -> (unit, string) Task.t -> t
  (** [write_file] is {!val:Yocaml.Action.write_static_file}. *)

  val write_file_with_metadata : Path.t -> (unit, 'a * string) Task.t -> t
  (** [write_file path task cache] is {!val:Yocaml.Action.write_static_file}
      that discards the metadata part. *)
end

module Dynamic : sig
  (** Utilities for dealing with tasks with dynamic dependencies. *)

  val write_file : Path.t -> (unit, string * Deps.t) Task.t -> t
  (** [write_file] is {!val:Yocaml.Action.write_dynamic_file}. *)

  val write_file_with_metadata :
    Path.t -> (unit, ('a * string) * Deps.t) Task.t -> t
  (** [write_file path task cache] is {!val:Yocaml.Action.write_dynamic_file}
      that discards the metadata part. *)
end
