from __future__ import annotations

import inspect
import re
from typing import Any

_STACK_INPUT_PATTERN = re.compile(r"^lora_stack(?:_([ab])|(\d+))$")


def _is_stack_input(name: str) -> bool:
    return bool(_STACK_INPUT_PATTERN.match(name))


def _stack_slot_number(name: str) -> int:
    """Numeric slot used to order stack inputs; legacy a/b map to 1/2."""
    match = _STACK_INPUT_PATTERN.match(name)
    if not match:
        return -1
    letter, digits = match.group(1), match.group(2)
    if digits is not None:
        return int(digits)
    return 1 if letter == "a" else 2


class _LoraStackOptionalInputs:
    """Lookup that preserves explicit optional inputs and dynamic lora_stack slots."""

    def __init__(self, explicit_inputs: dict[str, tuple[str, dict[str, Any]]]) -> None:
        self._explicit_inputs = explicit_inputs

    def __contains__(self, item: object) -> bool:
        if not isinstance(item, str):
            return False
        return item in self._explicit_inputs or _is_stack_input(item)

    def __getitem__(self, key: str) -> tuple[str, dict[str, Any]]:
        if key in self._explicit_inputs:
            return self._explicit_inputs[key]
        if _is_stack_input(key):
            return (
                "LORA_STACK",
                {
                    "tooltip": "A LoRA stack to combine. Connect to add more inputs.",
                },
            )
        raise KeyError(key)


class LoraStackCombinerLM:
    NAME = "Lora Stack Combiner (LoraManager)"
    CATEGORY = "Lora Manager/stackers"
    DESCRIPTION = (
        "Combines multiple LoRA stacks into a single stack. "
        "Supports dynamic inputs: connect a stack to add more inputs."
    )

    @classmethod
    def INPUT_TYPES(cls):
        optional_inputs: dict[str, tuple[str, dict[str, Any]]] = {
            "lora_stack1": (
                "LORA_STACK",
                {
                    "tooltip": "A LoRA stack to combine. Connect to add more inputs.",
                },
            ),
            "lora_stack2": (
                "LORA_STACK",
                {
                    "tooltip": "A LoRA stack to combine. Connect to add more inputs.",
                },
            ),
        }

        stack = inspect.stack()
        if len(stack) > 2 and stack[2].function == "get_input_info":
            optional_inputs = _LoraStackOptionalInputs(optional_inputs)  # pyright: ignore[reportAssignmentType]

        return {
            "required": {},
            "optional": optional_inputs,
        }

    RETURN_TYPES = ("LORA_STACK",)
    RETURN_NAMES = ("LORA_STACK",)
    FUNCTION = "combine_stacks"

    def combine_stacks(self, lora_stack1=None, lora_stack2=None, **kwargs):
        stacks = {
            "lora_stack1": lora_stack1,
            "lora_stack2": lora_stack2,
        }
        for key, value in kwargs.items():
            if _is_stack_input(key) and value is not None:
                stacks[key] = value

        combined_stack = []
        for key in sorted(stacks, key=_stack_slot_number):
            stack = stacks[key]
            if stack:
                combined_stack.extend(stack)

        return (combined_stack,)
