"""Robot library providing helpers for inline Python scripts."""

from __future__ import annotations


def fix_python_indentation(script: str) -> str:
    """Reconstruct indentation lost when Robot strips leading spaces.

    Args:
        script: Source code with indentation removed from the start of each line.

    Returns:
        Script with indentation restored so Python can execute it.
    """
    lines = script.split("\n")
    result: list[str] = []
    indent_level = 0
    # Each entry is a tuple (keyword, base_indent, has_seen_except_finally) where:
    #   keyword   — "try", "class", or "other"
    #   base_indent — the indent level at which the block keyword was written
    #   has_seen_except_finally — True once an except/finally clause has been seen
    #                             for this try block (used to detect when the inner
    #                             try is done and an outer try's except/finally follows)
    block_stack: list[tuple[str, int, bool]] = []

    for line in lines:
        stripped = line.strip()

        if not stripped:
            result.append("")
            continue

        is_dedenting = stripped.startswith(
            ("else:", "elif ", "except:", "except ", "finally:")
        )

        # Check if we should dedent from a class/function body back to module level
        # This happens when we see import, class, or a top-level statement after a class
        is_module_level_statement = False

        # If we're inside a class and see an import, we need to dedent
        if (
            indent_level > 0
            and block_stack
            and (
                stripped.startswith(("import ", "from "))
                or (stripped.startswith("class ") and block_stack[-1][0] == "class")
            )
        ):
            is_module_level_statement = True

        if is_dedenting and block_stack:
            if stripped.startswith("finally:"):
                # ``finally`` always belongs to the innermost ``try`` on the stack.
                # Pop non-try entries above it, set indent to the try's level,
                # then pop the try itself (``finally`` closes the try block).
                while block_stack and block_stack[-1][0] != "try":
                    block_stack.pop()
                if block_stack and block_stack[-1][0] == "try":
                    indent_level = block_stack[-1][1]
                    block_stack.pop()
                else:
                    indent_level = max(0, indent_level - 1)

            elif stripped.startswith(("except:", "except ")):
                # ``except`` belongs to the innermost ``try``.  However, if the
                # innermost ``try`` has already seen an ``except``/``finally``
                # clause AND there is an outer ``try`` on the stack, this
                # ``except`` belongs to the outer ``try`` (the inner try block
                # is complete).
                while block_stack and block_stack[-1][0] != "try":
                    block_stack.pop()

                if block_stack and block_stack[-1][0] == "try":
                    innermost_try = block_stack[-1]
                    has_outer_try = any(b[0] == "try" for b in block_stack[:-1])

                    if innermost_try[2] and has_outer_try:
                        # Inner try is done — pop it and find the outer try.
                        block_stack.pop()
                        while block_stack and block_stack[-1][0] != "try":
                            block_stack.pop()

                    if block_stack and block_stack[-1][0] == "try":
                        indent_level = block_stack[-1][1]
                        # Mark this try as having seen an except/finally clause.
                        block_stack[-1] = (
                            block_stack[-1][0],
                            block_stack[-1][1],
                            True,
                        )
                    else:
                        indent_level = max(0, indent_level - 1)
                else:
                    indent_level = max(0, indent_level - 1)

            else:
                # ``else`` / ``elif`` — dedent one level from the preceding body.
                indent_level = max(0, indent_level - 1)

        elif is_module_level_statement:
            # Dedent back to module level
            indent_level = 0
            block_stack.clear()

        current_indent = indent_level
        result.append("    " * current_indent + stripped)

        # Update the stack and indent level for the *next* line.
        if stripped.startswith("try:"):
            block_stack.append(("try", current_indent, False))
            indent_level += 1
        elif stripped.startswith("finally:"):
            # The matching ``try`` was already popped during the dedent phase above.
            indent_level += 1
        elif stripped.startswith(("except:", "except ")):
            # The try remains on the stack (more except/finally may follow).
            indent_level += 1
        elif stripped.endswith(":") and not is_dedenting:
            if stripped.startswith("class "):
                block_stack.append(("class", current_indent, False))
            else:
                block_stack.append(("other", current_indent, False))
            indent_level += 1
        elif is_dedenting and stripped.endswith(":"):
            indent_level += 1

    return "\n".join(result)
