# ToUpload/parsers/parse_scl.py
# -*- coding: utf-8 -*-
from lxml import etree
import re

# Importar desde las utilidades del parser
from .parser_utils import ns, get_multilingual_text

def reconstruct_scl_from_tokens(st_node):
    """
    Reconstruye SCL desde <StructuredText>, mejorando el manejo de
    variables, constantes literales, tokens básicos, espacios y saltos de línea.
    """
    if st_node is None:
        return "// Error: StructuredText node not found.\n"

    scl_parts = []
    # Usar st:* para obtener todos los elementos hijos dentro del namespace st
    children = st_node.xpath("./st:*", namespaces=ns)

    for elem in children:
        tag = etree.QName(elem.tag).localname

        if tag == "Token":
            scl_parts.append(elem.get("Text", ""))
        elif tag == "Blank":
            # Añadir espacios solo si es necesario o más de uno
            num_spaces = int(elem.get("Num", 1))
            if not scl_parts or not scl_parts[-1].endswith(" "):
                 scl_parts.append(" " * num_spaces)
            elif num_spaces > 1:
                 scl_parts.append(" " * (num_spaces -1))

        elif tag == "NewLine":
            # Quitar espacios finales antes del salto de línea
            if scl_parts:
                scl_parts[-1] = scl_parts[-1].rstrip()
            scl_parts.append("\n")
        elif tag == "Access":
            scope = elem.get("Scope")
            access_str = f"/*_ERR_Scope_{scope}_*/" # Placeholder

            # --- Variables ---
            if scope in [
                "GlobalVariable", "LocalVariable", "TempVariable", "InOutVariable",
                "InputVariable", "OutputVariable", "ConstantVariable",
                "GlobalConstant", "LocalConstant" # Añadir constantes simbólicas
                ]:
                symbol_elem = elem.xpath("./st:Symbol", namespaces=ns)
                if symbol_elem:
                    components = symbol_elem[0].xpath("./st:Component", namespaces=ns)
                    symbol_text_parts = []
                    for i, comp in enumerate(components):
                        name = comp.get("Name", "_ERR_COMP_")
                        if i > 0: symbol_text_parts.append(".")

                        # Check for HasQuotes attribute (adjust namespace if needed)
                        # El atributo está en el Component o en el Access padre? Probar ambos
                        has_quotes_comp = comp.get("HasQuotes", "false").lower() == "true" # Check directly on Component
                        has_quotes_access = False
                        access_parent = comp.xpath("ancestor::st:Access[1]", namespaces=ns) # Get immediate Access parent
                        if access_parent:
                             has_quotes_attr = access_parent[0].xpath("./st:BooleanAttribute[@Name='HasQuotes']/text()", namespaces=ns)
                             has_quotes_access = has_quotes_attr and has_quotes_attr[0].lower() == 'true'

                        has_quotes = has_quotes_comp or has_quotes_access
                        is_temp = name.startswith("#")

                        # Apply quotes based on HasQuotes or if it's the first component and not temp
                        if has_quotes or (i == 0 and not is_temp and '"' not in name): # Avoid double quotes
                            symbol_text_parts.append(f'"{name}"')
                        else:
                            symbol_text_parts.append(name)

                        # --- Array Index Access ---
                        index_access_nodes = comp.xpath("./st:Access", namespaces=ns)
                        if index_access_nodes:
                            # Llamada recursiva para cada índice
                            indices_text = [reconstruct_scl_from_tokens(idx_node) for idx_node in index_access_nodes]
                            # Limpiar saltos de línea dentro de los corchetes
                            indices_cleaned = [idx.replace('\n', '').strip() for idx in indices_text]
                            symbol_text_parts.append(f"[{','.join(indices_cleaned)}]")

                    access_str = "".join(symbol_text_parts)
                else:
                     access_str = f"/*_ERR_NO_SYMBOL_IN_{scope}_*/"

            # --- Constantes Literales ---
            elif scope == "LiteralConstant":
                constant_elem = elem.xpath("./st:Constant", namespaces=ns)
                if constant_elem:
                    val_elem = constant_elem[0].xpath("./st:ConstantValue/text()", namespaces=ns)
                    type_elem = constant_elem[0].xpath("./st:ConstantType/text()", namespaces=ns)
                    const_type = type_elem[0].strip().lower() if type_elem and type_elem[0] is not None else ""
                    const_val = val_elem[0].strip() if val_elem and val_elem[0] is not None else "_ERR_CONSTVAL_"

                    # Formatear según tipo
                    if const_type == "bool": access_str = const_val.upper()
                    elif const_type.lower() == "string": 
                        replaced_val = const_val.replace("'", "''")
                        access_str = f"'{replaced_val}'"
                    elif const_type.lower() == "char": 
                        replaced_val = const_val.replace("'", "''")
                        access_str = f"'{replaced_val}'"
                    elif const_type == "wstring": 
                        replaced_val = const_val.replace("'", "''")
                        access_str = f"WSTRING#'{replaced_val}'"
                    elif const_type == "wchar": 
                        replaced_val = const_val.replace("'", "''")
                        access_str = f"WCHAR#'{replaced_val}'"
                    elif const_type == "time": access_str = f"T#{const_val}"
                    elif const_type == "ltime": access_str = f"LT#{const_val}"
                    elif const_type == "s5time": access_str = f"S5T#{const_val}"
                    elif const_type == "date": access_str = f"D#{const_val}"
                    elif const_type == "dtl": access_str = f"DTL#{const_val}"
                    elif const_type == "dt": access_str = f"DT#{const_val}"
                    elif const_type == "tod": access_str = f"TOD#{const_val}"
                    elif const_type in ["int", "dint", "sint", "usint", "uint", "udint", "real", "lreal", "word", "dword", "byte"]:
                         # Añadir .0 para reales si no tienen decimal
                         if const_type in ["real", "lreal"] and '.' not in const_val and 'e' not in const_val.lower():
                             access_str = f"{const_val}.0"
                         else:
                             access_str = const_val
                    else: # Otros tipos (LWORD, etc.) o desconocidos
                        access_str = const_val
                else:
                    access_str = "/*_ERR_NOCONST_*/"

            # --- Llamadas a Funciones/Bloques (Scope=Call) ---
            elif scope == "Call":
                 call_info_node = elem.xpath("./st:CallInfo", namespaces=ns)
                 if call_info_node:
                     ci = call_info_node[0]
                     call_name = ci.get("Name", "_ERR_CALLNAME_")
                     call_type = ci.get("BlockType") # FB, FC, etc.

                     # Parámetros (están como Access o Token dentro de CallInfo/Parameter)
                     params = ci.xpath("./st:Parameter", namespaces=ns)
                     param_parts = []
                     for p in params:
                         p_name = p.get("Name", "_ERR_PARAMNAME_")
                         # El valor del parámetro está dentro del nodo Parameter
                         p_value_node = p.xpath("./st:Access | ./st:Token", namespaces=ns) # Buscar Access o Token
                         p_value_scl = ""
                         if p_value_node:
                             p_value_scl = reconstruct_scl_from_tokens(p) # Parsear el contenido del parámetro
                             p_value_scl = p_value_scl.replace('\n', '').strip() # Limpiar SCL resultante
                         param_parts.append(f"{p_name} := {p_value_scl}")

                     # Manejar FB vs FC
                     if call_type == "FB":
                         instance_node = ci.xpath("./st:Instance/st:Component/@Name", namespaces=ns)
                         if instance_node:
                             instance_name = f'"{instance_node[0]}"'
                             access_str = f"{instance_name}({', '.join(param_parts)})"
                         else: # FB sin instancia? Podría ser STAT
                              access_str = f'"{call_name}"({", ".join(param_parts)}) (* FB sin instancia explícita? *)'
                     elif call_type == "FC":
                          access_str = f'"{call_name}"({", ".join(param_parts)})'
                     else: # Otros tipos de llamada
                         access_str = f'"{call_name}"({", ".join(param_parts)}) (* Tipo: {call_type} *)'
                 else:
                      access_str = "/*_ERR_NO_CALLINFO_*/"

            # Añadir más scopes si son necesarios (e.g., Address, Label, Reference)

            scl_parts.append(access_str)

        elif tag == "Comment" or tag == "LineComment":
            # Usar get_multilingual_text del parser_utils
            comment_text = get_multilingual_text(elem)
            if tag == "Comment":
                scl_parts.append(f"(* {comment_text} *)")
            else:
                scl_parts.append(f"// {comment_text}")
        # Ignorar otros tipos de nodos si no son relevantes para el SCL

    full_scl = "".join(scl_parts)

    # --- Re-indentación Simple ---
    output_lines = []
    indent_level = 0
    indent_str = "  " # Dos espacios
    for line in full_scl.splitlines():
        trimmed_line = line.strip()
        if not trimmed_line:
            # Mantener líneas vacías? Opcional.
            # output_lines.append("")
            continue

        # Reducir indentación ANTES de imprimir para END, ELSE, etc.
        if trimmed_line.upper().startswith(("END_", "UNTIL", "}")) or \
           trimmed_line.upper() in ["ELSE", "ELSIF"]:
            indent_level = max(0, indent_level - 1)

        output_lines.append(indent_str * indent_level + trimmed_line)

        # Aumentar indentación DESPUÉS de imprimir para IF, FOR, etc.
        # Ser más específico con las palabras clave que aumentan indentación
        # Usar .upper() para ignorar mayúsculas/minúsculas
        line_upper = trimmed_line.upper()
        if line_upper.endswith(("THEN", "DO", "OF", "{")) or \
           line_upper.startswith(("IF ", "FOR ", "WHILE ", "CASE ", "REPEAT", "STRUCT")) or \
           line_upper == "ELSE":
             # Excepción: No indentar después de ELSE IF
             if not (line_upper == "ELSE" and "IF" in output_lines[-1].upper()):
                  indent_level += 1

    return "\n".join(output_lines)


def parse_scl_network(network_element):
    """
    Parsea una red SCL extrayendo el código fuente reconstruido.
    Devuelve un diccionario representando la red para el JSON.
    """
    network_id = network_element.get("ID", "UnknownSCL_ID")
    network_lang = "SCL"  # Sabemos que es SCL

    # --- Obtener título y comentario para coherencia con otros parsers ---
    title_elem = network_element.xpath(
        "./ObjectList/MultilingualText[@CompositionName='Title']", namespaces=ns
    )
    network_title = get_multilingual_text(title_elem[0]) if title_elem else f"Network {network_id}"

    comment_elem = network_element.xpath(
        "./ObjectList/MultilingualText[@CompositionName='Comment']", namespaces=ns
    )
    network_comment = get_multilingual_text(comment_elem[0]) if comment_elem else ""

    # --- Buscar NetworkSource y StructuredText sin depender del namespace ---
    network_source_node = network_element.xpath(".//*[local-name()='NetworkSource']")
    structured_text_node = None
    if network_source_node:
        st_nodes = network_source_node[0].xpath(".//*[local-name()='StructuredText']")
        if st_nodes:
            structured_text_node = st_nodes[0]

    reconstructed_scl = "// SCL extraction failed: StructuredText node not found.\n"
    if structured_text_node is not None:
        reconstructed_scl = reconstruct_scl_from_tokens(structured_text_node)

    parsed_network_data = {
        "id": network_id,
        "title": network_title,
        "comment": network_comment,
        "language": network_lang,
        "logic": [
            {
                "instruction_uid": f"SCL_{network_id}",
                "type": "RAW_SCL_CHUNK",
                "scl": reconstructed_scl,
            }
        ],
    }
    return parsed_network_data

# --- Función de Información del Parser ---
def get_parser_info():
    """Devuelve la información para este parser."""
    return {
        'language': ['SCL'], # Lista de lenguajes soportados
        'parser_func': parse_scl_network # Función a llamar
    }