#!/usr/bin/env python3
"""
Script para migrar tilemaps de archivos TMX (Tiled) a archivos YAML.

Uso:
    python tools/tmx_to_yaml.py

Busca todos los archivos .tmx en data/room/ y actualiza el tilemap
en el archivo .yaml correspondiente.
"""

import xml.etree.ElementTree as ET
from pathlib import Path
import re


def parse_layer_csv(data_element, width: int, height: int, offset: int) -> list[list[int]]:
    """
    Parsea los datos CSV de una capa y convierte los indices.

    Args:
        data_element: Elemento XML con los datos CSV
        width: Ancho del mapa en tiles
        height: Alto del mapa en tiles
        offset: Valor a restar de los indices (excepto 0 que pasa a -1)

    Returns:
        Lista de filas, cada fila es una lista de indices
    """
    encoding = data_element.attrib.get('encoding', '')
    if encoding != 'csv':
        raise ValueError(f"Encoding no soportado: {encoding}. Solo se soporta CSV.")

    csv_text = data_element.text.strip()
    all_values = []
    for val in csv_text.replace('\n', ',').split(','):
        val = val.strip()
        if val:
            all_values.append(int(val))

    # Convertir indices: 0 -> -1, N -> N - offset
    result = []
    for y in range(height):
        row = []
        for x in range(width):
            idx = y * width + x
            tmx_val = all_values[idx]
            if tmx_val == 0:
                yaml_val = -1
            else:
                yaml_val = tmx_val - offset
            row.append(yaml_val)
        result.append(row)

    return result


def parse_tmx(tmx_path: Path) -> tuple[list[list[int]], list[list[int]] | None, int, int]:
    """
    Parsea un archivo TMX y extrae el tilemap y collisionmap.

    Returns:
        tilemap: Lista de filas para el tilemap (layer id=1)
        collisionmap: Lista de filas para el collisionmap (layer id=2), o None si no existe
        width: Ancho del mapa en tiles
        height: Alto del mapa en tiles
    """
    tree = ET.parse(tmx_path)
    root = tree.getroot()

    width = int(root.attrib['width'])
    height = int(root.attrib['height'])

    # Obtener firstgid del primer tileset (para tilemap)
    tileset = root.find('tileset')
    firstgid = int(tileset.attrib['firstgid']) if tileset is not None else 1

    # Buscar las capas por id
    layers = root.findall('layer')
    layer1 = None
    layer2 = None
    for layer in layers:
        layer_id = layer.attrib.get('id')
        if layer_id == '1':
            layer1 = layer
        elif layer_id == '2':
            layer2 = layer

    # Parsear layer 1 (tilemap) - obligatoria
    if layer1 is None:
        raise ValueError(f"No se encontro layer id=1 en {tmx_path}")

    data1 = layer1.find('data')
    if data1 is None:
        raise ValueError(f"No se encontraron datos en layer 1 de {tmx_path}")

    tilemap = parse_layer_csv(data1, width, height, firstgid)

    # Parsear layer 2 (collisionmap) - opcional
    collisionmap = None
    if layer2 is not None:
        data2 = layer2.find('data')
        if data2 is not None:
            # Para collisionmap: 0 -> -1, N -> N - 576
            collisionmap = parse_layer_csv(data2, width, height, 576)

    return tilemap, collisionmap, width, height


def format_tilemap_yaml(tilemap: list[list[int]], width: int, height: int) -> str:
    """
    Formatea el tilemap como YAML.
    """
    lines = []
    lines.append(f"# Tilemap: {height} filas x {width} columnas ({width*8}x{height*8} pixeles @ 8px/tile)")
    lines.append("# Indices de tiles (-1 = vacio)")
    lines.append("tilemap:")

    for row in tilemap:
        row_str = ", ".join(str(v) for v in row)
        lines.append(f"  - [{row_str}]")

    return "\n".join(lines)


def format_collisionmap_yaml(collisionmap: list[list[int]], width: int, height: int) -> str:
    """
    Formatea el collisionmap como YAML.
    """
    lines = []
    lines.append(f"# Collisionmap: {height} filas x {width} columnas")
    lines.append("# Indices de colision (-1 = vacio, 1 = solido, 2 = plataforma)")
    lines.append("collisionmap:")

    for row in collisionmap:
        row_str = ", ".join(str(v) for v in row)
        lines.append(f"  - [{row_str}]")

    return "\n".join(lines)


def update_yaml_tilemap(yaml_path: Path, tilemap: list[list[int]], collisionmap: list[list[int]] | None, width: int, height: int) -> bool:
    """
    Actualiza la seccion tilemap y collisionmap de un archivo YAML.

    Returns:
        True si se actualizo correctamente, False si hubo error
    """
    if not yaml_path.exists():
        print(f"  ADVERTENCIA: {yaml_path} no existe, saltando...")
        return False

    content = yaml_path.read_text(encoding='utf-8')

    # Buscar donde empieza la seccion tilemap (incluyendo comentarios previos)
    # Capturar cualquier bloque de comentarios seguidos justo antes de tilemap:
    tilemap_pattern = re.compile(
        r'(#[^\n]*\n)*'       # Cualquier comentario antes de tilemap
        r'tilemap:\s*\n'      # Inicio de tilemap
        r'(  - \[.*\]\n?)+'   # Filas del tilemap
    )

    match = tilemap_pattern.search(content)
    if not match:
        print(f"  ERROR: No se encontro seccion tilemap en {yaml_path}")
        return False

    # Verificar que capturamos comentarios relacionados con tilemap
    # Si el match incluye comentarios no relacionados, ajustar el inicio
    matched_text = match.group(0)
    lines = matched_text.split('\n')

    # Buscar donde empiezan los comentarios de tilemap (los que queremos reemplazar)
    start_idx = 0
    for i, line in enumerate(lines):
        if line.startswith('#') and ('tilemap' in line.lower() or 'indice' in line.lower() or 'tile' in line.lower()):
            start_idx = i
            break
        elif line.startswith('tilemap:'):
            start_idx = i
            break

    # Ajustar el inicio del match si hay comentarios que no queremos eliminar
    if start_idx > 0:
        skip_chars = sum(len(lines[i]) + 1 for i in range(start_idx))
        actual_start = match.start() + skip_chars
    else:
        actual_start = match.start()

    # Generar nuevo tilemap
    new_tilemap = format_tilemap_yaml(tilemap, width, height)

    # Reemplazar desde actual_start (que puede ser ajustado si hay comentarios no relacionados)
    new_content = content[:actual_start] + new_tilemap + "\n" + content[match.end():]

    # Procesar collisionmap si existe
    if collisionmap is not None:
        new_collisionmap = format_collisionmap_yaml(collisionmap, width, height)

        # Buscar si ya existe seccion collisionmap
        collisionmap_pattern = re.compile(
            r'(#[^\n]*\n)*'           # Cualquier comentario antes de collisionmap
            r'collisionmap:\s*\n'     # Inicio de collisionmap
            r'(  - \[.*\]\n?)+'       # Filas del collisionmap
        )

        collision_match = collisionmap_pattern.search(new_content)
        if collision_match:
            # Reemplazar collisionmap existente
            collision_matched_text = collision_match.group(0)
            collision_lines = collision_matched_text.split('\n')

            # Buscar donde empiezan los comentarios de collisionmap
            collision_start_idx = 0
            for i, line in enumerate(collision_lines):
                if line.startswith('#') and ('collision' in line.lower()):
                    collision_start_idx = i
                    break
                elif line.startswith('collisionmap:'):
                    collision_start_idx = i
                    break

            if collision_start_idx > 0:
                skip_chars = sum(len(collision_lines[i]) + 1 for i in range(collision_start_idx))
                collision_actual_start = collision_match.start() + skip_chars
            else:
                collision_actual_start = collision_match.start()

            new_content = new_content[:collision_actual_start] + new_collisionmap + "\n" + new_content[collision_match.end():]
        else:
            # Insertar collisionmap despues del tilemap
            # Buscar el fin del tilemap recien insertado
            tilemap_end_pattern = re.compile(r'tilemap:\s*\n(  - \[.*\]\n?)+')
            tilemap_end_match = tilemap_end_pattern.search(new_content)
            if tilemap_end_match:
                insert_pos = tilemap_end_match.end()
                new_content = new_content[:insert_pos] + "\n" + new_collisionmap + "\n" + new_content[insert_pos:]

    yaml_path.write_text(new_content, encoding='utf-8')
    return True


def main():
    # Directorio de habitaciones
    script_dir = Path(__file__).parent
    room_dir = script_dir.parent / "data" / "room"

    if not room_dir.exists():
        print(f"ERROR: No se encontro directorio {room_dir}")
        return 1

    # Buscar archivos TMX
    tmx_files = sorted(room_dir.glob("*.tmx"))

    if not tmx_files:
        print("No se encontraron archivos .tmx en data/room/")
        return 0

    print(f"Encontrados {len(tmx_files)} archivos TMX")
    print()

    success_count = 0
    error_count = 0

    for tmx_path in tmx_files:
        yaml_path = tmx_path.with_suffix('.yaml')
        print(f"Procesando: {tmx_path.name} -> {yaml_path.name}")

        try:
            tilemap, collisionmap, width, height = parse_tmx(tmx_path)
            print(f"  TMX: {width}x{height} tiles")
            if collisionmap is not None:
                print(f"  Collisionmap detectado")

            if update_yaml_tilemap(yaml_path, tilemap, collisionmap, width, height):
                print(f"  OK: Tilemap actualizado")
                if collisionmap is not None:
                    print(f"  OK: Collisionmap actualizado")
                success_count += 1
            else:
                error_count += 1

        except Exception as e:
            print(f"  ERROR: {e}")
            error_count += 1

    print()
    print(f"Completado: {success_count} exitosos, {error_count} errores")
    return 0 if error_count == 0 else 1


if __name__ == "__main__":
    exit(main())
