#include <desktop_parser.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <sys/stat.h>
#include <ctype.h>
#include <pwd.h>
#include <unistd.h>

/* ── helpers ──────────────────────────────────────────────────────────────── */

static char *trim(char *s)
{
    while (isspace((unsigned char)*s)) s++;
    char *e = s + strlen(s);
    while (e > s && isspace((unsigned char)*(e-1))) e--;
    *e = '\0';
    return s;
}

/* ── applist ──────────────────────────────────────────────────────────────── */

void applist_init(AppList *list)
{
    list->capacity = 64;
    list->count    = 0;
    list->entries  = malloc(list->capacity * sizeof(DesktopEntry));
    if (!list->entries) { perror("malloc"); exit(1); }
}

void applist_free(AppList *list)
{
    free(list->entries);
    list->entries  = NULL;
    list->count    = 0;
    list->capacity = 0;
}

static DesktopEntry *applist_grow(AppList *list)
{
    if (list->count >= list->capacity) {
        list->capacity *= 2;
        list->entries   = realloc(list->entries,
                                  list->capacity * sizeof(DesktopEntry));
        if (!list->entries) { perror("realloc"); exit(1); }
    }
    DesktopEntry *e = &list->entries[list->count];
    memset(e, 0, sizeof(*e));
    return e;
}

/* ── exec field-code stripping ────────────────────────────────────────────── */

void exec_strip_field_codes(const char *src, char *dst, size_t dst_len)
{
    size_t di = 0;
    for (size_t i = 0; src[i] && di + 1 < dst_len; i++) {
        if (src[i] == '%' && src[i+1]) {
            i++; /* skip the code character */
            /* %% → literal % */
            if (src[i] == '%' && di + 1 < dst_len)
                dst[di++] = '%';
            /* all other codes are dropped */
        } else {
            dst[di++] = src[i];
        }
    }
    /* trim trailing whitespace left by removed codes */
    while (di > 0 && isspace((unsigned char)dst[di-1])) di--;
    dst[di] = '\0';
}

/* ── single file parse ────────────────────────────────────────────────────── */

int desktop_parse_file(const char *path, AppList *list)
{
    FILE *fp = fopen(path, "r");
    if (!fp) return 0;

    DesktopEntry entry;
    memset(&entry, 0, sizeof(entry));

    int in_desktop_entry = 0;
    char line[1024];

    while (fgets(line, sizeof(line), fp)) {
        char *l = trim(line);
        if (!*l || *l == '#') continue;

        /* section header */
        if (*l == '[') {
            in_desktop_entry = (strncmp(l, "[Desktop Entry]", 15) == 0);
            continue;
        }

        if (!in_desktop_entry) continue;

        char *eq = strchr(l, '=');
        if (!eq) continue;
        *eq = '\0';
        char *key = trim(l);
        char *val = trim(eq + 1);

        /* Only handle unlocalized keys (no [lang] suffix) */
        if (strchr(key, '[')) continue;

        if (strcmp(key, "Name") == 0)
            snprintf(entry.name, sizeof(entry.name), "%s", val);
        else if (strcmp(key, "Exec") == 0)
            exec_strip_field_codes(val, entry.exec, sizeof(entry.exec));
        else if (strcmp(key, "Icon") == 0)
            snprintf(entry.icon, sizeof(entry.icon), "%s", val);
        else if (strcmp(key, "Comment") == 0)
            snprintf(entry.comment, sizeof(entry.comment), "%s", val);
        else if (strcmp(key, "Categories") == 0)
            snprintf(entry.categories, sizeof(entry.categories), "%s", val);
        else if (strcmp(key, "Terminal") == 0)
            entry.terminal = (strcasecmp(val, "true") == 0);
        else if (strcmp(key, "NoDisplay") == 0 ||
                 strcmp(key, "Hidden") == 0) {
            if (strcasecmp(val, "true") == 0) entry.hidden = 1;
        } else if (strcmp(key, "Type") == 0) {
            if (strcmp(val, "Application") != 0) entry.hidden = 1;
        }
    }
    fclose(fp);

    if (entry.hidden || !entry.name[0] || !entry.exec[0])
        return 0;

    DesktopEntry *slot = applist_grow(list);
    *slot = entry;
    list->count++;
    return 1;
}

/* ── directory scan ───────────────────────────────────────────────────────── */

void desktop_scan_dir(const char *dir, AppList *list)
{
    DIR *d = opendir(dir);
    if (!d) return;

    struct dirent *de;
    char path[1024];

    while ((de = readdir(d)) != NULL) {
        if (de->d_name[0] == '.') continue;
        size_t nlen = strlen(de->d_name);
        if (nlen < 8 || strcmp(de->d_name + nlen - 8, ".desktop") != 0) continue;
        snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);

        /* Recurse one level into sub-dirs (some distros use subdirs) */
        struct stat st;
        if (stat(path, &st) == 0 && S_ISDIR(st.st_mode)) {
            desktop_scan_dir(path, list);
        } else {
            desktop_parse_file(path, list);
        }
    }
    closedir(d);
}

/* ── load from XDG locations ─────────────────────────────────────────────── */

void desktop_load_all(AppList *list)
{
    /* System-wide */
    desktop_scan_dir("/usr/share/applications", list);
    desktop_scan_dir("/usr/local/share/applications", list);

    /* Per-user override (parsed last → duplicates are intentionally kept;
       a more complete impl would deduplicate by app-id) */
    const char *home = getenv("HOME");
    if (!home) {
        struct passwd *pw = getpwuid(getuid());
        if (pw) home = pw->pw_dir;
    }
    if (home) {
        char buf[512];
        snprintf(buf, sizeof(buf), "%s/.local/share/applications", home);
        desktop_scan_dir(buf, list);
    }

    /* XDG_DATA_DIRS extra paths */
    const char *xdg = getenv("XDG_DATA_DIRS");
    if (xdg) {
        char dirs[2048];
        snprintf(dirs, sizeof(dirs), "%s", xdg);
        char *tok = strtok(dirs, ":");
        while (tok) {
            char buf[512];
            snprintf(buf, sizeof(buf), "%s/applications", tok);
            desktop_scan_dir(buf, list);
            tok = strtok(NULL, ":");
        }
    }
}