/**
 * @file sd_jpg_browser.c
 * @brief 💾 SD 卡 JPG 浏览与解码
 */

#include "sd_jpg_browser.h"

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

#include "driver/sdmmc_host.h"
#include "esp_check.h"
#include "esp_heap_caps.h"
#include "esp_jpeg_dec.h"
#include "esp_log.h"
#include "esp_vfs_fat.h"
#include "sdmmc_cmd.h"

#define SD_JPG_PIN_D1           GPIO_NUM_1
#define SD_JPG_PIN_D0           GPIO_NUM_2
#define SD_JPG_PIN_CLK          GPIO_NUM_3
#define SD_JPG_PIN_CMD          GPIO_NUM_4
#define SD_JPG_PIN_D3           GPIO_NUM_5
#define SD_JPG_PIN_D2           GPIO_NUM_6
#define SD_JPG_MAX_PATH_LEN     288
#define SD_JPG_MAX_NAME_LEN     256
#define SD_JPG_MAX_FILE_SIZE    (4 * 1024 * 1024)
#define SD_JPG_ALIGN_DOWN_8(v)  ((uint16_t)(((v) / 8U) * 8U))

typedef struct {
    char name[SD_JPG_MAX_NAME_LEN];
    char path[SD_JPG_MAX_PATH_LEN];
} sd_jpg_file_t;

static const char *TAG = "sd_jpg";
static sdmmc_card_t *s_card = NULL;
static bool s_mounted = false;
static sd_jpg_file_t s_files[SD_JPG_MAX_FILES] = {0};
static size_t s_file_count = 0;
static size_t s_scan_entry_count = 0;
static size_t s_scan_dir_count = 0;
static size_t s_unsupported_image_count = 0;

static bool sd_jpg_has_ext(const char *name)
{
    const char *dot = strrchr(name, '.');
    if (dot == NULL) {
        return false;
    }

    char ext[6] = {0};
    size_t i = 0;
    ++dot;
    while (dot[i] != '\0' && i < sizeof(ext) - 1) {
        ext[i] = (char)tolower((unsigned char)dot[i]);
        ++i;
    }

    return strcmp(ext, "jpg") == 0 || strcmp(ext, "jpeg") == 0;
}

static bool sd_jpg_has_unsupported_image_ext(const char *name)
{
    const char *dot = strrchr(name, '.');
    if (dot == NULL) {
        return false;
    }

    char ext[6] = {0};
    size_t i = 0;
    ++dot;
    while (dot[i] != '\0' && i < sizeof(ext) - 1) {
        ext[i] = (char)tolower((unsigned char)dot[i]);
        ++i;
    }

    return strcmp(ext, "png") == 0 || strcmp(ext, "bmp") == 0 ||
           strcmp(ext, "gif") == 0 || strcmp(ext, "webp") == 0;
}

static int sd_jpg_file_compare(const void *a, const void *b)
{
    const sd_jpg_file_t *fa = (const sd_jpg_file_t *)a;
    const sd_jpg_file_t *fb = (const sd_jpg_file_t *)b;
    return strcmp(fa->name, fb->name);
}

static bool sd_jpg_join_path(char *out, size_t out_len, const char *dir, const char *name)
{
    const size_t dir_len = strlen(dir);
    const size_t name_len = strlen(name);
    const bool need_slash = (dir_len > 0 && dir[dir_len - 1] != '/');
    const size_t total = dir_len + (need_slash ? 1 : 0) + name_len + 1;
    if (total > out_len) {
        ESP_LOGW(TAG, "⚠️ SD 文件路径过长，已跳过: %s/%s", dir, name);
        return false;
    }

    memcpy(out, dir, dir_len);
    size_t pos = dir_len;
    if (need_slash) {
        out[pos++] = '/';
    }
    memcpy(out + pos, name, name_len + 1);
    return true;
}

static void sd_jpg_add_file(const char *path, const char *name)
{
    if (s_file_count >= SD_JPG_MAX_FILES) {
        return;
    }

    sd_jpg_file_t *file = &s_files[s_file_count];
    snprintf(file->name, sizeof(file->name), "%s", name);
    snprintf(file->path, sizeof(file->path), "%s", path);
    ++s_file_count;
}

static void sd_jpg_scan_dir(const char *dir_path, int depth)
{
    DIR *dir = opendir(dir_path);
    if (dir == NULL) {
        ESP_LOGW(TAG, "⚠️ SD 卡目录打开失败: %s", dir_path);
        return;
    }

    struct dirent *entry = NULL;
    while ((entry = readdir(dir)) != NULL && s_file_count < SD_JPG_MAX_FILES) {
        if (strcmp(entry->d_name, ".") == 0 || strcmp(entry->d_name, "..") == 0) {
            continue;
        }

        char path[SD_JPG_MAX_PATH_LEN] = {0};
        if (!sd_jpg_join_path(path, sizeof(path), dir_path, entry->d_name)) {
            continue;
        }

        struct stat st = {0};
        if (stat(path, &st) != 0) {
            ESP_LOGW(TAG, "⚠️ SD 文件状态读取失败: %s", path);
            continue;
        }

        if (S_ISDIR(st.st_mode)) {
            ++s_scan_dir_count;
            ESP_LOGI(TAG, "📄 扫描 SD 目录: %s", path);
            if (depth < 2) {
                sd_jpg_scan_dir(path, depth + 1);
            }
            continue;
        }

        ++s_scan_entry_count;
        if (sd_jpg_has_ext(entry->d_name)) {
            sd_jpg_add_file(path, entry->d_name);
            ESP_LOGI(TAG, "📄 发现 JPG 图片: %s", path);
        } else if (sd_jpg_has_unsupported_image_ext(entry->d_name)) {
            ++s_unsupported_image_count;
            ESP_LOGI(TAG, "📄 发现非 JPG 图片: %s", path);
        } else if (s_scan_entry_count <= 24) {
            ESP_LOGI(TAG, "📄 跳过非 JPG 文件: %s", path);
        }
    }

    closedir(dir);
}

static esp_err_t sd_jpg_mount_if_needed(void)
{
    if (s_mounted) {
        return ESP_OK;
    }

    ESP_LOGI(TAG, "💾 初始化 SDMMC 并挂载 SD 卡...");

    esp_vfs_fat_sdmmc_mount_config_t mount_config = {
        .format_if_mount_failed = false,
        .max_files = 6,
        .allocation_unit_size = 16 * 1024,
    };

    sdmmc_host_t host = SDMMC_HOST_DEFAULT();
    host.max_freq_khz = 10000;

    sdmmc_slot_config_t slot_config = SDMMC_SLOT_CONFIG_DEFAULT();
    slot_config.width = 4;
    slot_config.clk = SD_JPG_PIN_CLK;
    slot_config.cmd = SD_JPG_PIN_CMD;
    slot_config.d0 = SD_JPG_PIN_D0;
    slot_config.d1 = SD_JPG_PIN_D1;
    slot_config.d2 = SD_JPG_PIN_D2;
    slot_config.d3 = SD_JPG_PIN_D3;
    slot_config.cd = SDMMC_SLOT_NO_CD;
    slot_config.wp = SDMMC_SLOT_NO_WP;
    slot_config.flags |= SDMMC_SLOT_FLAG_INTERNAL_PULLUP;

    esp_err_t ret = esp_vfs_fat_sdmmc_mount(SD_JPG_MOUNT_POINT, &host, &slot_config, &mount_config, &s_card);
    if (ret != ESP_OK) {
        ESP_LOGW(TAG, "⚠️ SD 卡挂载失败: %s", esp_err_to_name(ret));
        s_card = NULL;
        s_mounted = false;
        return ret;
    }

    s_mounted = true;
    ESP_LOGI(TAG, "✅ SD 卡挂载完成，CLK=%d CMD=%d D0=%d D1=%d D2=%d D3=%d",
             SD_JPG_PIN_CLK, SD_JPG_PIN_CMD, SD_JPG_PIN_D0, SD_JPG_PIN_D1, SD_JPG_PIN_D2, SD_JPG_PIN_D3);
    return ESP_OK;
}

esp_err_t sd_jpg_browser_rescan(void)
{
    esp_err_t ret = sd_jpg_mount_if_needed();
    if (ret != ESP_OK) {
        s_file_count = 0;
        return ret;
    }

    s_file_count = 0;
    s_scan_entry_count = 0;
    s_scan_dir_count = 0;
    s_unsupported_image_count = 0;
    sd_jpg_scan_dir(SD_JPG_MOUNT_POINT, 0);
    qsort(s_files, s_file_count, sizeof(s_files[0]), sd_jpg_file_compare);
    ESP_LOGI(TAG, "✅ SD 卡 JPG 扫描完成，共 %u 张，非 JPG 图片 %u 张，扫描文件 %u 个、目录 %u 个",
             (unsigned int)s_file_count, (unsigned int)s_unsupported_image_count,
             (unsigned int)s_scan_entry_count, (unsigned int)s_scan_dir_count);
    return ESP_OK;
}

esp_err_t sd_jpg_browser_init(void)
{
    return sd_jpg_browser_rescan();
}

bool sd_jpg_browser_is_mounted(void)
{
    return s_mounted;
}

size_t sd_jpg_browser_count(void)
{
    return s_file_count;
}

size_t sd_jpg_browser_unsupported_image_count(void)
{
    return s_unsupported_image_count;
}

const char *sd_jpg_browser_name(size_t index)
{
    if (index >= s_file_count) {
        return "--";
    }
    return s_files[index].name;
}

const char *sd_jpg_browser_path(size_t index)
{
    if (index >= s_file_count) {
        return NULL;
    }
    return s_files[index].path;
}

static esp_err_t sd_jpg_read_file(const char *path, uint8_t **out_data, size_t *out_size)
{
    FILE *file = fopen(path, "rb");
    if (file == NULL) {
        ESP_LOGW(TAG, "⚠️ JPG 文件打开失败: %s", path);
        return ESP_FAIL;
    }

    if (fseek(file, 0, SEEK_END) != 0) {
        fclose(file);
        return ESP_FAIL;
    }
    const long file_size = ftell(file);
    if (file_size <= 0 || file_size > SD_JPG_MAX_FILE_SIZE) {
        fclose(file);
        ESP_LOGW(TAG, "⚠️ JPG 文件大小异常: %s size=%ld", path, file_size);
        return ESP_ERR_INVALID_SIZE;
    }
    rewind(file);

    uint8_t *data = heap_caps_malloc((size_t)file_size, MALLOC_CAP_SPIRAM | MALLOC_CAP_8BIT);
    if (data == NULL) {
        data = heap_caps_malloc((size_t)file_size, MALLOC_CAP_8BIT);
    }
    if (data == NULL) {
        fclose(file);
        ESP_LOGW(TAG, "⚠️ JPG 输入缓冲分配失败: %ld bytes", file_size);
        return ESP_ERR_NO_MEM;
    }

    const size_t read_size = fread(data, 1, (size_t)file_size, file);
    fclose(file);
    if (read_size != (size_t)file_size) {
        free(data);
        ESP_LOGW(TAG, "⚠️ JPG 文件读取不完整: %s", path);
        return ESP_FAIL;
    }

    *out_data = data;
    *out_size = read_size;
    return ESP_OK;
}

static void sd_jpg_calc_fit(uint16_t src_w, uint16_t src_h, uint16_t max_w, uint16_t max_h,
                            uint16_t *dst_w, uint16_t *dst_h)
{
    if (src_w <= max_w && src_h <= max_h) {
        *dst_w = 0;
        *dst_h = 0;
        return;
    }

    uint32_t w_by_h = ((uint32_t)src_w * max_h) / src_h;
    uint32_t h_by_w = ((uint32_t)src_h * max_w) / src_w;
    if (w_by_h <= max_w) {
        *dst_w = SD_JPG_ALIGN_DOWN_8(w_by_h);
        *dst_h = SD_JPG_ALIGN_DOWN_8(max_h);
    } else {
        *dst_w = SD_JPG_ALIGN_DOWN_8(max_w);
        *dst_h = SD_JPG_ALIGN_DOWN_8(h_by_w);
    }

    if (*dst_w < 8) {
        *dst_w = 8;
    }
    if (*dst_h < 8) {
        *dst_h = 8;
    }
}

esp_err_t sd_jpg_browser_decode_fit(size_t index, uint16_t max_w, uint16_t max_h, sd_jpg_image_t *out_image)
{
    if (out_image == NULL || index >= s_file_count) {
        return ESP_ERR_INVALID_ARG;
    }

    memset(out_image, 0, sizeof(*out_image));
    const char *path = sd_jpg_browser_path(index);
    uint8_t *input = NULL;
    size_t input_size = 0;
    ESP_RETURN_ON_ERROR(sd_jpg_read_file(path, &input, &input_size), TAG, "⚠️ JPG 文件读取失败");

    jpeg_dec_handle_t jpeg = NULL;
    jpeg_dec_config_t config = DEFAULT_JPEG_DEC_CONFIG();
    config.output_type = JPEG_PIXEL_FORMAT_RGB565_LE;

    jpeg_error_t jpeg_ret = jpeg_dec_open(&config, &jpeg);
    if (jpeg_ret != JPEG_ERR_OK) {
        free(input);
        ESP_LOGW(TAG, "⚠️ JPG 解码器创建失败: %d", (int)jpeg_ret);
        return ESP_FAIL;
    }

    jpeg_dec_io_t io = {
        .inbuf = input,
        .inbuf_len = (int)input_size,
        .inbuf_remain = (int)input_size,
    };
    jpeg_dec_header_info_t header = {0};
    jpeg_ret = jpeg_dec_parse_header(jpeg, &io, &header);
    jpeg_dec_close(jpeg);
    jpeg = NULL;
    if (jpeg_ret != JPEG_ERR_OK || header.width == 0 || header.height == 0) {
        free(input);
        ESP_LOGW(TAG, "⚠️ JPG 头解析失败: %s err=%d", path, (int)jpeg_ret);
        return ESP_FAIL;
    }

    uint16_t fit_w = 0;
    uint16_t fit_h = 0;
    sd_jpg_calc_fit(header.width, header.height, max_w, max_h, &fit_w, &fit_h);

    config = (jpeg_dec_config_t)DEFAULT_JPEG_DEC_CONFIG();
    config.output_type = JPEG_PIXEL_FORMAT_RGB565_LE;
    config.scale.width = fit_w;
    config.scale.height = fit_h;
    jpeg_ret = jpeg_dec_open(&config, &jpeg);
    if (jpeg_ret != JPEG_ERR_OK) {
        free(input);
        ESP_LOGW(TAG, "⚠️ JPG 解码器创建失败: %d", (int)jpeg_ret);
        return ESP_FAIL;
    }

    io = (jpeg_dec_io_t) {
        .inbuf = input,
        .inbuf_len = (int)input_size,
        .inbuf_remain = (int)input_size,
    };
    jpeg_dec_header_info_t out_info = {0};
    jpeg_ret = jpeg_dec_parse_header(jpeg, &io, &out_info);
    if (jpeg_ret != JPEG_ERR_OK) {
        jpeg_dec_close(jpeg);
        free(input);
        ESP_LOGW(TAG, "⚠️ JPG 输出参数解析失败: %s err=%d", path, (int)jpeg_ret);
        return ESP_FAIL;
    }

    int out_len = 0;
    jpeg_ret = jpeg_dec_get_outbuf_len(jpeg, &out_len);
    if (jpeg_ret != JPEG_ERR_OK || out_len <= 0) {
        jpeg_dec_close(jpeg);
        free(input);
        ESP_LOGW(TAG, "⚠️ JPG 输出缓冲大小获取失败: %d", (int)jpeg_ret);
        return ESP_FAIL;
    }

    uint8_t *output = jpeg_calloc_align((size_t)out_len, 16);
    if (output == NULL) {
        jpeg_dec_close(jpeg);
        free(input);
        ESP_LOGW(TAG, "⚠️ JPG 输出缓冲分配失败: %d bytes", out_len);
        return ESP_ERR_NO_MEM;
    }

    io.outbuf = output;
    io.out_size = out_len;
    jpeg_ret = jpeg_dec_process(jpeg, &io);
    jpeg_dec_close(jpeg);
    free(input);

    if (jpeg_ret != JPEG_ERR_OK) {
        jpeg_free_align(output);
        ESP_LOGW(TAG, "⚠️ JPG 解码失败: %s err=%d", path, (int)jpeg_ret);
        return ESP_FAIL;
    }

    out_image->data = output;
    out_image->data_size = (size_t)out_len;
    out_image->width = out_info.width;
    out_image->height = out_info.height;
    ESP_LOGI(TAG, "✅ JPG 解码完成: %s %ux%u -> %ux%u",
             sd_jpg_browser_name(index), header.width, header.height, out_image->width, out_image->height);
    return ESP_OK;
}

void sd_jpg_browser_free_image(sd_jpg_image_t *image)
{
    if (image == NULL || image->data == NULL) {
        return;
    }
    jpeg_free_align(image->data);
    memset(image, 0, sizeof(*image));
}
