// SPDX-License-Identifier: GPL-2.0-only
/*
 *  ppm/src/util/path.c
 *
 *  Copyright (C) 2026 Project Tick
 */

#ifdef HAVE_CONFIG_H
#include <config.h>
#endif

#include "path.h"

#include <dirent.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <unistd.h>

#include "log.h"

ppm_error_t ppm_path_join(const char *a, const char *b, char **out) {
    if (!a || !b || !out) return PPM_ERR_INVALID;
    size_t la = strlen(a), lb = strlen(b);

    /* Strip trailing '/' from `a` and leading '/' from `b` so we never
     * produce double slashes in the middle. */
    while (la > 0 && a[la - 1] == '/') la--;
    while (lb > 0 && b[0] == '/') { b++; lb--; }

    /* Special case: caller asked for an absolute root. */
    if (la == 0 && a[0] == '/') {
        char *p = NULL;
        if (asprintf(&p, "/%.*s", (int)lb, b) < 0) return PPM_ERR_NOMEM;
        *out = p;
        return PPM_OK;
    }

    char *p = NULL;
    if (la == 0) {
        p = strndup(b, lb);
    } else if (lb == 0) {
        p = strndup(a, la);
    } else {
        if (asprintf(&p, "%.*s/%.*s", (int)la, a, (int)lb, b) < 0) p = NULL;
    }
    if (!p) return PPM_ERR_NOMEM;
    *out = p;
    return PPM_OK;
}

int ppm_path_is_dir(const char *path) {
    struct stat st;
    if (stat(path, &st) != 0) return 0;
    return S_ISDIR(st.st_mode);
}

ppm_error_t ppm_path_mkdirs(const char *path, unsigned mode) {
    if (!path || !*path) return PPM_ERR_INVALID;

    char *copy = strdup(path);
    if (!copy) return PPM_ERR_NOMEM;

    /* Walk component by component, creating each as we go. We rewrite
     * separators in place so the prefix is always NUL-terminated. */
    for (char *p = copy + 1; ; p++) {
        char saved = *p;
        if (saved == '/' || saved == '\0') {
            *p = '\0';
            if (*copy && mkdir(copy, (mode_t)mode) != 0) {
                if (errno != EEXIST) {
                    int e = errno;
                    free(copy);
                    errno = e;
                    return (e == EACCES || e == EPERM)
                           ? PPM_ERR_PERMISSION : PPM_ERR_IO;
                }
                /* Existing entry must be a directory. */
                struct stat st;
                if (stat(copy, &st) != 0 || !S_ISDIR(st.st_mode)) {
                    free(copy);
                    return PPM_ERR_EXISTS;
                }
            }
            *p = saved;
            if (saved == '\0') break;
        }
    }
    free(copy);
    return PPM_OK;
}

static ppm_error_t rmrf_at(int dirfd, const char *name) {
    struct stat st;
    if (fstatat(dirfd, name, &st, AT_SYMLINK_NOFOLLOW) != 0) {
        return (errno == ENOENT) ? PPM_OK : PPM_ERR_IO;
    }
    if (S_ISDIR(st.st_mode)) {
        int sub = openat(dirfd, name, O_RDONLY | O_DIRECTORY | O_NOFOLLOW | O_CLOEXEC);
        if (sub < 0) return PPM_ERR_IO;
        DIR *d = fdopendir(sub);
        if (!d) { close(sub); return PPM_ERR_IO; }
        struct dirent *de;
        ppm_error_t err = PPM_OK;
        while ((de = readdir(d)) != NULL) {
            if (strcmp(de->d_name, ".") == 0 || strcmp(de->d_name, "..") == 0)
                continue;
            ppm_error_t e = rmrf_at(sub, de->d_name);
            if (e && !err) err = e;
        }
        closedir(d); /* also closes sub */
        if (unlinkat(dirfd, name, AT_REMOVEDIR) != 0 && errno != ENOENT) {
            if (!err) err = PPM_ERR_IO;
        }
        return err;
    }
    if (unlinkat(dirfd, name, 0) != 0 && errno != ENOENT) return PPM_ERR_IO;
    return PPM_OK;
}

ppm_error_t ppm_path_rmrf(const char *path) {
    if (!path || !*path) return PPM_ERR_INVALID;

    struct stat st;
    if (lstat(path, &st) != 0) {
        return (errno == ENOENT) ? PPM_OK : PPM_ERR_IO;
    }
    if (!S_ISDIR(st.st_mode)) {
        return (unlink(path) == 0 || errno == ENOENT) ? PPM_OK : PPM_ERR_IO;
    }
    /* Split into dirname + basename so we can use the *at family. */
    char *copy = strdup(path);
    if (!copy) return PPM_ERR_NOMEM;
    char *slash = strrchr(copy, '/');
    const char *parent;
    const char *name;
    if (!slash) {
        parent = ".";
        name   = copy;
    } else if (slash == copy) {
        parent = "/";
        name   = slash + 1;
    } else {
        *slash = '\0';
        parent = copy;
        name   = slash + 1;
    }
    int pfd = open(parent, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
    if (pfd < 0) { free(copy); return PPM_ERR_IO; }
    ppm_error_t err = rmrf_at(pfd, name);
    close(pfd);
    free(copy);
    return err;
}

ppm_error_t ppm_path_fsync_dir(const char *path) {
    if (!path) return PPM_ERR_INVALID;
    int fd = open(path, O_RDONLY | O_DIRECTORY | O_CLOEXEC);
    if (fd < 0) return PPM_ERR_IO;
    int rc = fsync(fd);
    int e = errno;
    close(fd);
    if (rc != 0 && e != EINVAL && e != ENOTSUP) return PPM_ERR_IO;
    return PPM_OK;
}

ppm_error_t ppm_path_fdatasync(int fd) {
    int rc;
#if defined(HAVE_FDATASYNC)
    /*
     * fdatasync(2) may live in the system library but lack a declaration in
     * certain SDK headers (e.g. macOS).  Provide our own prototype so that
     * the compiler accepts the call when HAVE_FDATASYNC is set by configure.
     */
    extern int fdatasync(int);
    rc = fdatasync(fd);
#else
    rc = fsync(fd);
#endif
    if (rc != 0 && errno != EINVAL && errno != ENOTSUP) return PPM_ERR_IO;
    return PPM_OK;
}
