"""ND0401: Detect broken (unbalanced) n8n expression brackets."""

from collections.abc import Iterator
from pathlib import Path
from typing import Any

from valn8n_lib.rules.registry import SourceMap, register
from valn8n_lib.source_map import lookup_line
from valn8n_lib.types import Diagnostic, Location, RuleCode, Severity, SuggestionLevel

_RULE_CODE = RuleCode("ND0401", Severity.ERROR)


def _check_expression_brackets(value: str) -> str | None:
    """Check bracket balance in a string value.

    Returns an error message if unbalanced, or None if balanced.
    Only strings starting with '={{' are considered n8n expressions.
    """
    if not value.startswith("={{"):
        return None

    depth = 0
    pos = 0
    length = len(value)

    while pos < length:
        next_open = value.find("{{", pos)
        next_close = value.find("}}", pos)

        if next_open == -1 and next_close == -1:
            break

        # Pick whichever comes first
        if next_close == -1 or (next_open != -1 and next_open <= next_close):
            depth += 1
            pos = next_open + 2
        else:
            depth -= 1
            if depth < 0:
                return "unexpected `}}`"
            pos = next_close + 2

    if depth > 0:
        return "unclosed `{{`"
    return None


def _walk_strings(obj: Any, path_parts: list[str]) -> Iterator[tuple[str, str]]:
    """Recursively yield (field_path, string_value) for every string leaf."""
    if isinstance(obj, str):
        yield "/".join(path_parts), obj
    elif isinstance(obj, dict):
        for key, val in obj.items():
            yield from _walk_strings(val, [*path_parts, key])
    elif isinstance(obj, list):
        for idx, val in enumerate(obj):
            yield from _walk_strings(val, [*path_parts, str(idx)])


@register("ND0401", Severity.ERROR, description="Unbalanced expression brackets")
def check_broken_expressions(
    workflow: dict[str, Any],
    path: Path,
    source_map: SourceMap = None,
    suggestions: SuggestionLevel = SuggestionLevel.FULL,
) -> list[Diagnostic]:
    """Detect unbalanced {{ / }} in node parameter string values."""
    diagnostics: list[Diagnostic] = []
    fname = str(path)

    for i, node in enumerate(workflow.get("nodes", [])):
        node_name = node.get("name", f"node[{i}]")
        parameters = node.get("parameters")
        if not isinstance(parameters, dict):
            continue

        for field_path, value in _walk_strings(parameters, []):
            error = _check_expression_brackets(value)
            if error is None:
                continue

            pointer = f"/nodes/{i}/parameters/{field_path}"
            line = lookup_line(source_map, pointer)
            diagnostics.append(
                Diagnostic(
                    file=fname,
                    location=Location(node_name, f"parameters/{field_path}"),
                    code=_RULE_CODE,
                    message=f"Broken expression: {error} in value: {value!r}",
                    line=line,
                ),
            )

    return diagnostics
