!> @file path_util.f90
!> @brief Case-relative path resolution for file-valued namelist parameters.
!!
!! Policy (see doc/superpowers/specs/2026-07-04-case-paths-inline-config-mp-gather):
!! every file the namelist references (grid, and later restart/data/output) is
!! resolved against the **case directory** — by default the directory of the
!! namelist itself — with ABSOLUTE paths honored as-is. This is the OpenFOAM
!! `-case` / `#include`-relative model: a case is a movable folder, resolvable
!! regardless of the process working directory.
module path_util
  implicit none
  private

  public :: resolve_case_path, path_dirname, is_absolute_path, case_base_dir

contains

  !> True if `path` is absolute: POSIX "/…", a Windows drive "X:\…"/"X:/…",
  !! or a UNC "\\…" path. Used so a user-supplied absolute grid path is left
  !! untouched while relative paths are anchored to the case directory.
  pure logical function is_absolute_path(path) result(is_abs)
    character(len=*), intent(in) :: path
    integer :: n
    character :: c1
    is_abs = .false.
    n = len_trim(path)
    if (n == 0) return
    c1 = path(1:1)
    if (c1 == '/' .or. c1 == '\') then
      is_abs = .true.
    else if (n >= 3) then
      ! Windows drive-ABSOLUTE path, e.g. C:\ or C:/. A drive letter followed
      ! by ':' but NOT a separator (e.g. C:cases) is drive-*relative* — relative
      ! to the cwd on that drive — so it is deliberately NOT treated as absolute.
      if (((c1 >= 'A' .and. c1 <= 'Z') .or. (c1 >= 'a' .and. c1 <= 'z')) &
          .and. path(2:2) == ':' .and. (path(3:3) == '\' .or. path(3:3) == '/')) &
        is_abs = .true.
    end if
  end function is_absolute_path

  !> The case directory used to anchor a namelist's relative file paths: the
  !! explicit `override` when supplied and non-empty (the OpenFOAM `-case` /
  !! GUI-supplied case dir), otherwise the namelist file's own directory. This
  !! is the single place the "override-or-namelist-dir" rule lives.
  pure function case_base_dir(nml_path, override) result(dir)
    character(len=*), intent(in) :: nml_path
    character(len=*), intent(in), optional :: override
    character(len=:), allocatable :: dir
    if (present(override)) then
      if (len_trim(override) > 0) then
        dir = trim(override)
        return
      end if
    end if
    dir = trim(path_dirname(nml_path))
  end function case_base_dir

  !> Directory part of `path` (everything up to the last '/' or '\'); '.' when
  !! `path` has no directory component. The trailing separator is dropped.
  pure function path_dirname(path) result(dir)
    character(len=*), intent(in) :: path
    character(len=len(path)) :: dir
    integer :: i, isep
    isep = 0
    do i = len_trim(path), 1, -1
      if (path(i:i) == '/' .or. path(i:i) == '\') then
        isep = i
        exit
      end if
    end do
    if (isep == 0) then
      dir = '.'
    else if (isep == 1) then
      dir = path(1:1)   ! root "/" (or "\")
    else
      dir = path(1:isep - 1)
    end if
  end function path_dirname

  !> Resolve a namelist file-valued parameter against a case directory.
  !! Absolute or empty `value` is returned unchanged; a relative `value` is
  !! joined onto `base_dir` with a '/' (accepted by the runtime on both POSIX
  !! and Windows). A '.' or empty `base_dir` leaves `value` unchanged.
  pure function resolve_case_path(base_dir, value) result(path)
    character(len=*), intent(in) :: base_dir, value
    character(len=len_trim(base_dir) + len_trim(value) + 1) :: path
    integer :: nb
    character :: last
    nb = len_trim(base_dir)
    if (len_trim(value) == 0 .or. is_absolute_path(value) &
        .or. nb == 0 .or. trim(base_dir) == '.') then
      path = value
    else
      ! Don't double the separator when base_dir already ends in one (e.g. the
      ! filesystem root '/' or '\'), which on POSIX would make an
      ! implementation-defined leading '//'.
      last = base_dir(nb:nb)
      if (last == '/' .or. last == '\') then
        path = trim(base_dir)//trim(value)
      else
        path = trim(base_dir)//'/'//trim(value)
      end if
    end if
  end function resolve_case_path

end module path_util
