/*
 * Beat Rat -- fuzzy file/directory picker
 *
 * Matching is simple subsequence scoring, the same basic idea fzf and
 * snippysnappy's OpenPromptApp use: every character of the typed query
 * must appear in the name in order (not necessarily adjacent), and
 * consecutive matches score higher than scattered ones, so tighter
 * matches sort to the top. Rescans the directory on every keystroke
 * rather than caching -- simple, and directory listings here are small
 * enough that this is not worth optimizing yet.
 *
 * The shortcut tab row (Default / Projects / any alternate directories
 * from Settings) is what lets Import hot-swap its source directory
 * without closing the picker -- clicking a tab just re-scans a
 * different base_dir in place.
 */

#include "file_picker.h"
#include "ui.h"
#include "log.h"

#include <ctype.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <strings.h>
#include <sys/stat.h>

typedef struct {
    char name[FILEPICKER_MAX_NAME];
    int score;
} Candidate;

static int fuzzy_score(const char *pattern, const char *text) {
    if (!*pattern)
        return 0; /* empty query: everything matches, equally */

    int score = 0, consecutive = 0;
    const char *p = pattern;
    for (const char *t = text; *t && *p; t++) {
        if (tolower((unsigned char)*t) == tolower((unsigned char)*p)) {
            score += 10 + consecutive * 5; /* reward runs of consecutive matches */
            consecutive++;
            p++;
        } else {
            consecutive = 0;
        }
    }
    return (*p == '\0') ? score : -1; /* -1 = pattern wasn't a full subsequence */
}

static int has_wav_ext(const char *name) {
    size_t len = strlen(name);
    return len >= 4 && strcasecmp(name + len - 4, ".wav") == 0;
}

/* Returns 1 if this directory entry belongs in the listing for the
 * picker's current mode (a .wav file, or a subdirectory). */
static int entry_matches_mode(const FilePicker *fp, const char *name) {
    if (fp->mode == FILEPICKER_MODE_WAV_FILES)
        return has_wav_ext(name);

    /* FILEPICKER_MODE_PROJECT_DIRS */
    if (strcmp(name, ".") == 0 || strcmp(name, "..") == 0)
        return 0;
    char full[1024];
    snprintf(full, sizeof(full), "%s/%s", fp->base_dir, name);
    struct stat st;
    return (stat(full, &st) == 0 && S_ISDIR(st.st_mode));
}

static void rescan(FilePicker *fp) {
    fp->num_matches = 0;

    Candidate candidates[128];
    int num_candidates = 0;

    DIR *d = opendir(fp->base_dir);
    if (!d) {
        LOG_WARN("file picker: could not open directory '%s'", fp->base_dir);
        return;
    }

    struct dirent *ent;
    while ((ent = readdir(d)) != NULL && num_candidates < 128) {
        if (!entry_matches_mode(fp, ent->d_name))
            continue;
        int score = fuzzy_score(fp->query, ent->d_name);
        if (score < 0)
            continue;
        snprintf(candidates[num_candidates].name, FILEPICKER_MAX_NAME, "%s", ent->d_name);
        candidates[num_candidates].score = score;
        num_candidates++;
    }
    closedir(d);

    /* Simple insertion sort, descending by score -- candidate counts here
     * are small (capped at 128), so this is plenty fast. */
    for (int i = 1; i < num_candidates; i++) {
        Candidate key = candidates[i];
        int j = i - 1;
        while (j >= 0 && candidates[j].score < key.score) {
            candidates[j + 1] = candidates[j];
            j--;
        }
        candidates[j + 1] = key;
    }

    int n = num_candidates < FILEPICKER_MAX_MATCHES ? num_candidates : FILEPICKER_MAX_MATCHES;
    for (int i = 0; i < n; i++)
        memcpy(fp->matches[i], candidates[i].name, FILEPICKER_MAX_NAME);
    fp->num_matches = n;

    if (fp->selected >= fp->num_matches)
        fp->selected = fp->num_matches > 0 ? fp->num_matches - 1 : 0;
}

void filepicker_open(FilePicker *fp, const char *base_dir, FilePickerMode mode) {
    snprintf(fp->base_dir, sizeof(fp->base_dir), "%s", base_dir);
    fp->mode = mode;
    fp->query[0] = '\0';
    fp->query_len = 0;
    fp->selected = 0;
    rescan(fp);
    LOG_INFO("file picker: opened on '%s' (%s mode), %d match(es) found", base_dir,
              mode == FILEPICKER_MODE_WAV_FILES ? "wav" : "project-dirs", fp->num_matches);
}

void filepicker_set_shortcuts(FilePicker *fp, const FilePickerShortcut *shortcuts, int num_shortcuts) {
    int n = num_shortcuts < FILEPICKER_MAX_SHORTCUTS ? num_shortcuts : FILEPICKER_MAX_SHORTCUTS;
    for (int i = 0; i < n; i++)
        fp->shortcuts[i] = shortcuts[i];
    fp->num_shortcuts = n;
}

void filepicker_append_char(FilePicker *fp, char c) {
    if (fp->query_len >= FILEPICKER_MAX_QUERY - 1)
        return;
    fp->query[fp->query_len++] = c;
    fp->query[fp->query_len] = '\0';
    fp->selected = 0;
    rescan(fp);
}

void filepicker_backspace(FilePicker *fp) {
    if (fp->query_len == 0)
        return;
    fp->query[--fp->query_len] = '\0';
    fp->selected = 0;
    rescan(fp);
}

void filepicker_move_selection(FilePicker *fp, int delta) {
    if (fp->num_matches == 0)
        return;
    fp->selected += delta;
    if (fp->selected < 0) fp->selected = 0;
    if (fp->selected >= fp->num_matches) fp->selected = fp->num_matches - 1;
}

int filepicker_selected_path(const FilePicker *fp, char *out, size_t out_size) {
    if (fp->num_matches == 0)
        return -1;
    snprintf(out, out_size, "%s/%s", fp->base_dir, fp->matches[fp->selected]);
    return 0;
}

int filepicker_shortcut_at(const FilePicker *fp, int win_w, int win_h, int click_x, int click_y) {
    if (fp->num_shortcuts == 0)
        return -1;

    int box_w = FILEPICKER_BOX_W, box_h = FILEPICKER_BOX_H;
    int x = (win_w - box_w) / 2;
    int y = (win_h - box_h) / 2;
    int row_y = y + FILEPICKER_SHORTCUT_ROW_Y;

    if (click_y < row_y || click_y >= row_y + FILEPICKER_SHORTCUT_ROW_H)
        return -1;

    int tab_w = (box_w - 8) / fp->num_shortcuts;
    int rel_x = click_x - (x + 4);
    if (rel_x < 0)
        return -1;
    int idx = rel_x / tab_w;
    if (idx < 0 || idx >= fp->num_shortcuts)
        return -1;
    return idx;
}

void filepicker_draw(const FilePicker *fp, Display *dpy, Window win, GC gc,
                      int win_w, int win_h) {
    int box_w = FILEPICKER_BOX_W, box_h = FILEPICKER_BOX_H;
    int x = (win_w - box_w) / 2;
    int y = (win_h - box_h) / 2;

    ui_draw_bevel(dpy, win, gc, x, y, box_w, box_h, 0);

    /* Query line */
    XSetForeground(dpy, gc, 0xFFFFFF);
    XFillRectangle(dpy, win, gc, x + 4, y + 4, box_w - 8, 24);
    XSetForeground(dpy, gc, 0x000000);
    char line[FILEPICKER_MAX_QUERY + 4];
    snprintf(line, sizeof(line), "> %s", fp->query);
    XDrawString(dpy, win, gc, x + 8, y + 20, line, (int)strlen(line));
    int cursor_x = x + 8 + (int)strlen(line) * 6 + 1;
    XDrawLine(dpy, win, gc, cursor_x, y + 8, cursor_x, y + 22);

    /* Shortcut tab row, if any -- the currently-open directory's tab is
     * shown pressed so it's obvious which one you're browsing. */
    int list_y = y + FILEPICKER_LIST_Y;
    if (fp->num_shortcuts > 0) {
        int row_y = y + FILEPICKER_SHORTCUT_ROW_Y;
        int tab_w = (box_w - 8) / fp->num_shortcuts;
        for (int i = 0; i < fp->num_shortcuts; i++) {
            int is_current = (strcmp(fp->shortcuts[i].path, fp->base_dir) == 0);
            ui_draw_button(dpy, win, gc, x + 4 + i * tab_w, row_y, tab_w - 2,
                           FILEPICKER_SHORTCUT_ROW_H - 2, fp->shortcuts[i].label, is_current);
        }
    } else {
        list_y = y + 34; /* no shortcut row -- list starts right under the query line */
    }

    /* Match list, selected row highlighted */
    int row_h = 20;
    for (int i = 0; i < fp->num_matches; i++) {
        int ry = list_y + i * row_h;
        if (ry + row_h > y + box_h - 4)
            break;
        if (i == fp->selected) {
            XSetForeground(dpy, gc, 0x0A246A);
            XFillRectangle(dpy, win, gc, x + 4, ry, box_w - 8, row_h);
            XSetForeground(dpy, gc, 0xFFFFFF);
        } else {
            XSetForeground(dpy, gc, 0x000000);
        }
        XDrawString(dpy, win, gc, x + 8, ry + 15, fp->matches[i], (int)strlen(fp->matches[i]));
    }

    if (fp->num_matches == 0) {
        XSetForeground(dpy, gc, 0x606060);
        const char *msg = (fp->mode == FILEPICKER_MODE_WAV_FILES)
                               ? "No matching .wav files"
                               : "No matching projects";
        XDrawString(dpy, win, gc, x + 8, list_y + 15, msg, (int)strlen(msg));
    }
}
