import {} from "Char"
import { complement, equals, identity, ifElse } from "Function"
import { drop, dropLast, filter, first, last } from "List"
import { Just, Nothing, fromMaybe } from "Maybe"
import String from "String"



export alias FilePath = String


/**
 * Drops the trailing slash of a path.
 *
 * @since 0.8.0
 * @example
 * dropTrailingPathSeparator("/path/") // "/path"
 */
dropTrailingPathSeparator :: FilePath -> FilePath
export dropTrailingPathSeparator = ifElse(
  (path) => path != "/" && String.lastChar(path) == Just('/'),
  String.dropLast(1),
  identity,
)


performSplitPath = (buffer, foundSlash, path) => where(String.firstChar(path)) {
  Nothing =>
    [buffer]

  Just('/') =>
    performSplitPath(buffer ++ "/", true, String.drop(1, path))

  Just(char) =>
    foundSlash
      ? mappend(
        [buffer],
        performSplitPath(String.prependChar(char, ""), false, String.drop(1, path)),
      )
      : performSplitPath(buffer ++ String.prependChar(char, ""), false, String.drop(1, path))
}


/**
 * Splits all segments of a path in a list.
 *
 * @since 0.8.0
 * @example
 * splitPath("/root/path") // ["/", "root/", "path"]
 */
splitPath :: FilePath -> List FilePath
export splitPath = performSplitPath("", false)


/**
 * Joins a list of path segments.
 *
 * @since 0.8.0
 * @example
 * joinPath(["/", "root/", "path"]) // "/root/path"
 */
joinPath :: List FilePath -> FilePath
export joinPath = pipe(
  filter(complement(String.isEmpty)),
  ifElse(
    pipe(
      first,
      equals(Just("/")),
    ),
    pipe(
      drop(1),
      map(dropTrailingPathSeparator),
      String.join("/"),
      mappend("/"),
    ),
    pipe(
      map(dropTrailingPathSeparator),
      String.join("/"),
    ),
  ),
)


/**
 * Removes all extra slashes from a path.
 *
 * @since 0.8.0
 * @example
 * canonicalizePath("/root//path") // "/root/path"
 */
canonicalizePath :: FilePath -> FilePath
export canonicalizePath = pipe(
  splitPath,
  map(
    pipe(
      ifElse(
        pipe(
          String.take(2),
          equals("./"),
        ),
        String.drop(2),
        identity,
      ),
      ifElse(
        pipe(
          String.lastChar,
          equals(Just('/')),
        ),
        String.replace("([^/]+)/*", "$1/"),
        identity,
      ),
    ),
  ),
  joinPath,
)


/**
 * Drops the given amount of segments from the beginning of the path.
 * Note that a leading / is considered as a path segment.
 *
 * @since 0.8.0
 * @example
 * dropPathSegments(2, "/root/path/extra") // "path/extra"
 */
dropPathSegments :: Integer -> FilePath -> FilePath
export dropPathSegments = (howMany) => pipe(
  splitPath,
  drop(howMany),
  joinPath,
)


/**
 * Returns the parent path of the given path.
 *
 * @since 0.8.0
 * @example
 * parentPath("/root/path") // "/root"
 */
parentPath :: FilePath -> FilePath
export parentPath = pipe(
  splitPath,
  dropLast(1),
  joinPath,
)


/**
 * Returns true if the first path is a parent path of the second path.
 *
 * @since 0.8.0
 * @example
 * isRootPathOf("/root/path", "/root/path/child")      // true
 * isRootPathOf("/root/different", "/root/path/child") // false
 */
isRootPathOf :: FilePath -> FilePath -> Boolean
export isRootPathOf = (root, path) => {
  rootParts = splitPath(root)
  pathParts = splitPath(path)

  rootStart = dropTrailingPathSeparator(fromMaybe("", first(rootParts)))
  pathStart = dropTrailingPathSeparator(fromMaybe("", first(pathParts)))

  return rootStart == pathStart || rootStart == ""
    ? rootStart == "" ? true : isRootPathOf(dropPathSegments(1, root), dropPathSegments(1, path))
    : false
}


/**
 * Extracts the filename part of a path if there is one, returns an empty string otherwise.
 *
 * @since 0.8.0
 * @example
 * takeFileName("/path/filename.ext") // "filename.ext"
 * takeFileName("/path/folder/")      // ""
 */
takeFileName :: FilePath -> FilePath
export takeFileName = pipe(
  splitPath,
  last,
  where {
    Just(part) =>
      String.lastChar(part) == Just('/') ? "" : part

    Nothing =>
      ""
  },
)


/**
 * Extracts the extension of a path if there's one, returns an empty string otherwise.
 *
 * @since 0.8.0
 * @example
 * takeExtension("/path/file.ext") // "ext"
 * takeExtension("/path/file")     // ""
 */
takeExtension :: FilePath -> FilePath
export takeExtension = pipe(
  takeFileName,
  String.split("."),
  last,
  map(ifElse(String.isEmpty, identity, mappend("."))),
  fromMaybe(""),
)
