#include "engine/tilemap.h"
#include "engine/camera.h"
#include "engine/assets.h"
#include <SDL2/SDL_image.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

/* Read a full line from f into a dynamically growing buffer.
 * *buf and *cap track the heap buffer; the caller must free *buf.
 * Returns the line length, or -1 on EOF/error. */
static int read_line(FILE *f, char **buf, int *cap) {
    if (!*buf) {
        *cap = 16384;
        *buf = malloc(*cap);
        if (!*buf) return -1;
    }
    int len = 0;
    for (;;) {
        if (len + 1 >= *cap) {
            int new_cap = *cap * 2;
            char *tmp = realloc(*buf, new_cap);
            if (!tmp) return -1;
            *buf = tmp;
            *cap = new_cap;
        }
        int ch = fgetc(f);
        if (ch == EOF) return len > 0 ? len : -1;
        (*buf)[len++] = (char)ch;
        if (ch == '\n') break;
    }
    (*buf)[len] = '\0';
    return len;
}

bool tilemap_load(Tilemap *map, const char *path, SDL_Renderer *renderer) {
    FILE *f = fopen(path, "r");
    if (!f) {
        fprintf(stderr, "Failed to open level: %s\n", path);
        return false;
    }

    memset(map, 0, sizeof(Tilemap));

    char *line = NULL;
    int   line_cap = 0;
    char  tileset_path[256] = {0};
    int   current_layer = -1; /* 0=collision, 1=bg, 2=fg */
    int   row = 0;

    while (read_line(f, &line, &line_cap) >= 0) {
        /* Skip comments and empty lines */
        if (line[0] == '#' || line[0] == '\n' || line[0] == '\r') continue;

        /* Parse directives */
        if (strncmp(line, "TILESET ", 8) == 0) {
            sscanf(line + 8, "%255s", tileset_path);
        } else if (strncmp(line, "SIZE ", 5) == 0) {
            sscanf(line + 5, "%d %d", &map->width, &map->height);
            if (map->width <= 0 || map->height <= 0 ||
                map->width > MAX_MAP_SIZE || map->height > MAX_MAP_SIZE) {
                fprintf(stderr, "Invalid map size: %dx%d\n", map->width, map->height);
                free(line);
                fclose(f);
                return false;
            }
            int total = map->width * map->height;
            map->collision_layer = calloc(total, sizeof(uint16_t));
            map->bg_layer        = calloc(total, sizeof(uint16_t));
            map->fg_layer        = calloc(total, sizeof(uint16_t));
            if (!map->collision_layer || !map->bg_layer || !map->fg_layer) {
                fprintf(stderr, "Failed to allocate tile layers (%dx%d)\n",
                        map->width, map->height);
                free(map->collision_layer);
                free(map->bg_layer);
                free(map->fg_layer);
                memset(map, 0, sizeof(Tilemap));
                free(line);
                fclose(f);
                return false;
            }
        } else if (strncmp(line, "SPAWN ", 6) == 0) {
            float sx, sy;
            sscanf(line + 6, "%f %f", &sx, &sy);
            map->player_spawn = vec2(sx * TILE_SIZE, sy * TILE_SIZE);
        } else if (strncmp(line, "GRAVITY ", 8) == 0) {
            sscanf(line + 8, "%f", &map->gravity);
        } else if (strncmp(line, "WIND ", 5) == 0) {
            sscanf(line + 5, "%f", &map->wind);
        } else if (strncmp(line, "TILEDEF ", 8) == 0) {
            int id, tx, ty;
            uint32_t flags;
            sscanf(line + 8, "%d %d %d %u", &id, &tx, &ty, &flags);
            if (id < MAX_TILE_DEFS) {
                map->tile_defs[id].tex_x = (uint16_t)tx;
                map->tile_defs[id].tex_y = (uint16_t)ty;
                map->tile_defs[id].flags = flags;
                if (id >= map->tile_def_count) {
                    map->tile_def_count = id + 1;
                }
            }
        } else if (strncmp(line, "BG_COLOR ", 9) == 0) {
            int r, g, b;
            if (sscanf(line + 9, "%d %d %d", &r, &g, &b) == 3) {
                map->bg_color = (SDL_Color){
                    (uint8_t)r, (uint8_t)g, (uint8_t)b, 255
                };
                map->has_bg_color = true;
            }
        } else if (strncmp(line, "PARALLAX_FAR ", 13) == 0) {
            sscanf(line + 13, "%255s", map->parallax_far_path);
        } else if (strncmp(line, "PARALLAX_NEAR ", 14) == 0) {
            sscanf(line + 14, "%255s", map->parallax_near_path);
        } else if (strncmp(line, "MUSIC ", 6) == 0) {
            sscanf(line + 6, "%255s", map->music_path);
        } else if (strncmp(line, "PARALLAX_STYLE ", 15) == 0) {
            int style = 0;
            if (sscanf(line + 15, "%d", &style) == 1) {
                map->parallax_style = style;
            }
        } else if (strncmp(line, "PLAYER_UNARMED", 14) == 0) {
            map->player_unarmed = true;
        } else if (strncmp(line, "EXIT ", 5) == 0) {
            if (map->exit_zone_count < MAX_EXIT_ZONES) {
                ExitZone *ez = &map->exit_zones[map->exit_zone_count];
                float tx, ty, tw, th;
                char target[ASSET_PATH_MAX] = {0};
                /* EXIT <tile_x> <tile_y> <tile_w> <tile_h> [target_path] */
                int n = sscanf(line + 5, "%f %f %f %f %255s",
                               &tx, &ty, &tw, &th, target);
                if (n >= 4) {
                    ez->x = tx * TILE_SIZE;
                    ez->y = ty * TILE_SIZE;
                    ez->w = tw * TILE_SIZE;
                    ez->h = th * TILE_SIZE;
                    if (n == 5) {
                        snprintf(ez->target, sizeof(ez->target), "%s", target);
                    } else {
                        ez->target[0] = '\0'; /* no target = victory */
                    }
                    map->exit_zone_count++;
                }
            }
        } else if (strncmp(line, "ENTITY ", 7) == 0) {
            if (map->entity_spawn_count < MAX_ENTITY_SPAWNS) {
                EntitySpawn *es = &map->entity_spawns[map->entity_spawn_count];
                float tx, ty;
                if (sscanf(line + 7, "%31s %f %f", es->type_name, &tx, &ty) == 3) {
                    es->x = tx * TILE_SIZE;
                    es->y = ty * TILE_SIZE;
                    map->entity_spawn_count++;
                }
            }
        } else if (strncmp(line, "LAYER ", 6) == 0) {
            row = 0;
            if (strstr(line, "collision")) current_layer = 0;
            else if (strstr(line, "bg"))    current_layer = 1;
            else if (strstr(line, "fg"))    current_layer = 2;
        } else if (current_layer >= 0 && map->width > 0) {
            /* Parse tile row */
            uint16_t *layer = NULL;
            switch (current_layer) {
                case 0: layer = map->collision_layer; break;
                case 1: layer = map->bg_layer; break;
                case 2: layer = map->fg_layer; break;
            }
            if (layer && row < map->height) {
                char *tok = strtok(line, " \t\n\r");
                for (int col = 0; col < map->width && tok; col++) {
                    layer[row * map->width + col] = (uint16_t)atoi(tok);
                    tok = strtok(NULL, " \t\n\r");
                }
                row++;
            }
        }
    }
    free(line);
    fclose(f);

    /* Load tileset texture */
    if (tileset_path[0] && renderer) {
        snprintf(map->tileset_path, sizeof(map->tileset_path), "%s", tileset_path);
        map->tileset = assets_get_texture(tileset_path);
        if (map->tileset) {
            int tex_w;
            SDL_QueryTexture(map->tileset, NULL, NULL, &tex_w, NULL);
            map->tileset_cols = tex_w / TILE_SIZE;
        }
    }

    printf("Loaded level: %s (%dx%d tiles)\n", path, map->width, map->height);
    return true;
}

void tilemap_render_layer(const Tilemap *map, const uint16_t *layer,
                           const Camera *cam, SDL_Renderer *renderer) {
    if (!map || !layer || !map->tileset) return;

    /* Only render visible tiles */
    int start_x = 0, start_y = 0;
    int end_x = map->width, end_y = map->height;

    if (cam) {
        float inv_zoom = (cam->zoom > 0.0f) ? (1.0f / cam->zoom) : 1.0f;
        start_x = (int)(cam->pos.x / TILE_SIZE) - 1;
        start_y = (int)(cam->pos.y / TILE_SIZE) - 1;
        end_x = start_x + (int)(cam->viewport.x * inv_zoom / TILE_SIZE) + 3;
        end_y = start_y + (int)(cam->viewport.y * inv_zoom / TILE_SIZE) + 3;

        if (start_x < 0) start_x = 0;
        if (start_y < 0) start_y = 0;
        if (end_x > map->width) end_x = map->width;
        if (end_y > map->height) end_y = map->height;
    }

    for (int y = start_y; y < end_y; y++) {
        for (int x = start_x; x < end_x; x++) {
            uint16_t tile_id = layer[y * map->width + x];
            if (tile_id == 0) continue; /* 0 = empty */

            /* Look up tile definition for source rect */
            SDL_Rect src;
            if (tile_id < map->tile_def_count) {
                src.x = map->tile_defs[tile_id].tex_x * TILE_SIZE;
                src.y = map->tile_defs[tile_id].tex_y * TILE_SIZE;
            } else {
                /* Fallback: tile_id maps directly to tileset grid position */
                int cols = map->tileset_cols > 0 ? map->tileset_cols : 16;
                src.x = ((tile_id - 1) % cols) * TILE_SIZE;
                src.y = ((tile_id - 1) / cols) * TILE_SIZE;
            }
            src.w = TILE_SIZE;
            src.h = TILE_SIZE;

            Vec2 world_pos = vec2(tile_to_world(x), tile_to_world(y));
            Vec2 screen_pos = cam ? camera_world_to_screen(cam, world_pos) : world_pos;

            float tile_draw_size = TILE_SIZE;
            if (cam && cam->zoom != 1.0f && cam->zoom > 0.0f) {
                tile_draw_size = TILE_SIZE * cam->zoom;
            }

            SDL_Rect dst = {
                (int)screen_pos.x,
                (int)screen_pos.y,
                (int)(tile_draw_size + 0.5f),
                (int)(tile_draw_size + 0.5f)
            };

            SDL_RenderCopy(renderer, map->tileset, &src, &dst);
        }
    }
}

bool tilemap_is_solid(const Tilemap *map, int tile_x, int tile_y) {
    if (!map) return false;
    if (tile_x < 0 || tile_x >= map->width) return true;  /* out of bounds = solid */
    if (tile_y < 0) return false;                          /* above map = open    */
    if (tile_y >= map->height) return true;                /* below map = solid   */

    uint16_t tile_id = map->collision_layer[tile_y * map->width + tile_x];
    if (tile_id == 0) return false;

    /* Check tile def flags */
    if (tile_id < map->tile_def_count) {
        return (map->tile_defs[tile_id].flags & TILE_SOLID) != 0;
    }

    /* Default: non-zero collision tile is solid */
    return true;
}

uint32_t tilemap_flags_at(const Tilemap *map, int tile_x, int tile_y) {
    if (!map) return 0;
    if (tile_x < 0 || tile_x >= map->width) return TILE_SOLID;
    if (tile_y < 0) return 0;
    if (tile_y >= map->height) return TILE_SOLID;

    uint16_t tile_id = map->collision_layer[tile_y * map->width + tile_x];
    if (tile_id == 0) return 0;

    if (tile_id < map->tile_def_count) {
        return map->tile_defs[tile_id].flags;
    }

    return TILE_SOLID; /* default non-zero = solid */
}

void tilemap_free(Tilemap *map) {
    if (!map) return;
    free(map->collision_layer);
    free(map->bg_layer);
    free(map->fg_layer);
    /* Don't free tileset - asset manager owns it */
    memset(map, 0, sizeof(Tilemap));
}
