#include "cvm_core.h"
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

static int file_exists(const char *p) {
    FILE *f = fopen(p, "rb");
    if (!f) return 0;
    fclose(f);
    return 1;
}

static int ends_with(const char *s, const char *suf) {
    size_t ls = strlen(s), lf = strlen(suf);
    if (ls < lf) return 0;
    return strcmp(s + ls - lf, suf) == 0;
}

/*
 * 以 base 目录为起点, 按相对 spec 的 / 段推进, 规整 . 与 .. 段。
 * 例: base="samples", spec="./cyc_b" -> "samples/cyc_b"
 *     base="samples", spec="../sib/foo" -> "sib/foo"
 * 用段级规整而非朴素拼接, 避免 "/./" 这类冗余分隔符导致 fopen 失败。
 */
static void resolve_relative(const char *base, const char *spec, char *out, size_t outsz) {
    char combined[512];
    if (base[0] == '\0') snprintf(combined, sizeof(combined), "%s", spec);
    else                 snprintf(combined, sizeof(combined), "%s/%s", base, spec);
    char result[512];
    result[0] = '\0';
    const char *p = combined;
    char seg[256];
    while (*p) {
        while (*p == '/' || *p == '\\') p++;
        if (!*p) break;
        int k = 0;
        while (*p && *p != '/' && *p != '\\' && k < 255) seg[k++] = *p++;
        seg[k] = '\0';
        if      (strcmp(seg, ".")  == 0) continue;
        else if (strcmp(seg, "..") == 0) {
            char *last = strrchr(result, '/');
            if (last) *last = '\0';
            else result[0] = '\0';
        } else {
            size_t l = strlen(result);
            if (l && l < sizeof(result) - 1) { result[l] = '/'; result[l + 1] = '\0'; }
            l = strlen(result);
            if (l + strlen(seg) + 1 < sizeof(result)) strcat(result, seg);
        }
    }
    snprintf(out, outsz, "%s", result);
}

/* 取路径的目录部分(不含末尾分隔符) */
static void dir_of(const char *path, char *buf, size_t bufsz) {
    const char *slash = strrchr(path, '/');
    const char *bslash = strrchr(path, '\\');
    const char *sep = NULL;
    if (slash && bslash) sep = (slash > bslash) ? slash : bslash;
    else if (slash) sep = slash;
    else if (bslash) sep = bslash;
    if (!sep) { buf[0] = '\0'; return; }
    size_t n = (size_t)(sep - path);
    if (n >= bufsz) n = bufsz - 1;
    memcpy(buf, path, n);
    buf[n] = '\0';
}

/*
 * 把 spec 解析为具体 .cvm 文件路径。base: 调用方所在脚本路径(用于相对解析), 可为空。
 * 解析策略(优先级从高到低):
 *   1) 显式 .cvm 后缀    -> 直接文件路径(绝对或相对 cwd), 原样使用
 *   2) 相对路径 ./ 或 ../ -> 相对 base 的目录拼接, 并补 .cvm / index.cvm
 *   3) 包形式(支持嵌套 a/b) -> packages/<spec>/index.cvm
 *                                packages/<spec>.cvm
 *                                packages/<spec>/<末段>.cvm
 * buf 由调用方提供(大小 >= CVM_MAX_LOAD_PATH); 内部用 snprintf 写入,
 * 因 buf 以指针传入, 不会触发 -Wformat-truncation。
 */
static void resolve_spec(const char *spec, const char *base, char *buf, size_t bufsz) {
    if (ends_with(spec, ".cvm")) {
        snprintf(buf, bufsz, "%s", spec);
        return;
    }
    int is_rel = (spec[0] == '.' && (spec[1] == '/' || spec[1] == '\\' ||
                                     (spec[1] == '.' && (spec[2] == '/' || spec[2] == '\\'))));
    if (is_rel) {
        char dir[256];
        dir_of(base, dir, sizeof(dir));            /* base 是调用方完整路径, 取其目录 */
        char resolved[512];
        resolve_relative(dir, spec, resolved, sizeof(resolved));
        snprintf(buf, bufsz, "%s.cvm", resolved);
        if (file_exists(buf)) return;
        snprintf(buf, bufsz, "%s/index.cvm", resolved);
        if (file_exists(buf)) return;
        snprintf(buf, bufsz, "%s.cvm", resolved); /* 兜底, 交给 fopen 报错 */
        return;
    }
    /* 包形式(支持嵌套 a/b) */
    const char *slash = strrchr(spec, '/');
    const char *bslash = strrchr(spec, '\\');
    const char *sep = NULL;
    if (slash && bslash) sep = (slash > bslash) ? slash : bslash;
    else if (slash) sep = slash;
    else if (bslash) sep = bslash;
    const char *base_name = sep ? sep + 1 : spec;

    snprintf(buf, bufsz, "packages/%s/index.cvm", spec);
    if (file_exists(buf)) return;
    snprintf(buf, bufsz, "packages/%s.cvm", spec);
    if (file_exists(buf)) return;
    snprintf(buf, bufsz, "packages/%s/%s.cvm", spec, base_name);
}

/* 有界拷贝当前执行文件路径(用 memcpy 避免 snprintf 的截断告警) */
static void set_current_file(Runtime *rt, const char *p) {
    size_t n = strlen(p);
    if (n > sizeof(rt->current_file) - 1) n = sizeof(rt->current_file) - 1;
    memcpy(rt->current_file, p, n);
    rt->current_file[n] = '\0';
}

Map *load_module(Runtime *rt, const char *spec) {
    char specbuf[192];
    snprintf(specbuf, sizeof(specbuf), "%s", spec);
    char path[CVM_MAX_LOAD_PATH];
    resolve_spec(specbuf, rt->current_file, path, sizeof(path));

    /* 循环依赖检测: path 是否已在加载链上 */
    for (int i = 0; i < rt->loading_n; ++i)
        if (strcmp(rt->loading_paths[i], path) == 0) {
            snprintf(rt->errbuf, sizeof(rt->errbuf),
                     "require: 检测到循环依赖 (重复加载 '%s')", specbuf);
            rt->has_error = 1;
            return NULL;
        }

    /* 缓存: 同一路径只加载一次 */
    Map *cached = (Map *)map_get(&rt->modules, path);
    if (cached) return cached;

    /* 压入加载链(本函数返回前 path 始终有效, 用副本留存) */
    if (rt->loading_n < CVM_MAX_LOAD_DEPTH)
        snprintf(rt->loading_paths[rt->loading_n++], CVM_MAX_LOAD_PATH, "%s", path);

    FILE *f = fopen(path, "rb");
    if (!f) {
        snprintf(rt->errbuf, sizeof(rt->errbuf),
                 "require: 找不到模块 '%s'", specbuf);
        rt->has_error = 1;
        rt->loading_n--;
        return NULL;
    }
    fseek(f, 0, SEEK_END); long sz = ftell(f); fseek(f, 0, SEEK_SET);
    char *src = (char *)malloc((size_t)sz + 1);
    size_t rd = fread(src, 1, (size_t)sz, f); src[rd] = '\0';
    fclose(f);

    Map *exp = NULL;
    rt->has_error = 0; rt->errbuf[0] = '\0';
    Program prog = cvm_compile(rt, src, path);
    free(src);
    if (!prog.has_error) {
        Env *menv = env_new(rt->global, &rt->arena);
        /* 切换 current_file 为本模块路径, 供其内部相对 require 使用; 结束再恢复 */
        char saved_file[512];
        snprintf(saved_file, sizeof(saved_file), "%s", rt->current_file);
        set_current_file(rt, path);
        cvm_exec_program(rt, &prog, menv);
        set_current_file(rt, saved_file);
        if (!rt->has_error) {
            exp = (Map *)malloc(sizeof(Map));
            map_init(exp);
            for (int i = 0; i < prog.count; ++i) {
                Stmt *s = prog.stmts[i];
                if (s->kind == STMT_LET) {
                    Value *v = env_get(menv, s->u.let.name);
                    if (v) map_set(exp, s->u.let.name, (void *)v);
                } else if (s->kind == STMT_FUNC) {
                    Value *fv = (Value *)arena_alloc(&rt->arena, sizeof(Value));
                    *fv = val_func(s->u.func.name);
                    map_set(exp, s->u.func.name, (void *)fv);
                }
            }
            map_set(&rt->modules, path, (void *)exp);
        }
    }
    rt->loading_n--;
    return exp;
}