from __future__ import annotations

import json
from typing import Any


def from_json(value: str, *, runtime: Any) -> Any:
    return json.loads(value)


def to_json(
    value: Any,
    indent: int | None = 2,
    sort_keys: bool = False,
    *,
    runtime: Any,
) -> str:
    return json.dumps(value, indent=indent, sort_keys=sort_keys)


def merge(
    left: dict[str, Any],
    right: dict[str, Any],
    deep: bool = False,
    *,
    runtime: Any,
) -> dict[str, Any]:
    merged = dict(left)
    for key, value in right.items():
        if deep and isinstance(merged.get(key), dict) and isinstance(value, dict):
            merged[key] = _merge_dicts(merged[key], value)
        else:
            merged[key] = value
    return merged


def template(template: str, values: dict[str, Any], *, runtime: Any) -> str:
    try:
        return template.format_map(_TemplateValues(values))
    except KeyError as error:
        missing = error.args[0]
        raise KeyError(f"missing template value: {missing}") from error


def select(value: Any, path: str, *, runtime: Any) -> Any:
    current = value
    for selector in _parse_path(path):
        try:
            if isinstance(selector, int):
                current = current[selector]
            elif isinstance(current, dict):
                current = current[selector]
            elif hasattr(current, selector):
                current = getattr(current, selector)
            else:
                current = current[selector]
        except (KeyError, IndexError, TypeError, AttributeError) as error:
            raise KeyError(f"unable to select path '{path}'") from error
    return current


def pick(value: dict[str, Any], keys: list[Any], *, runtime: Any) -> dict[str, Any]:
    normalized_keys = _normalize_keys(keys)
    missing = [key for key in normalized_keys if key not in value]
    if missing:
        raise KeyError(f"missing keys: {', '.join(missing)}")
    return {key: value[key] for key in normalized_keys}


def omit(value: dict[str, Any], keys: list[Any], *, runtime: Any) -> dict[str, Any]:
    blocked = set(_normalize_keys(keys))
    return {key: item for key, item in value.items() if key not in blocked}


def _merge_dicts(left: dict[str, Any], right: dict[str, Any]) -> dict[str, Any]:
    merged = dict(left)
    for key, value in right.items():
        if isinstance(merged.get(key), dict) and isinstance(value, dict):
            merged[key] = _merge_dicts(merged[key], value)
        else:
            merged[key] = value
    return merged


def _parse_path(path: str) -> list[str | int]:
    selectors: list[str | int] = []
    index = 0

    while index < len(path):
        if path[index] == ".":
            index += 1
            if index >= len(path):
                raise ValueError("invalid selector path")

        if index < len(path) and path[index] == "[":
            selector, index = _parse_bracket_selector(path, index)
            selectors.append(selector)
            continue

        start = index
        if index >= len(path) or not (path[index].isalpha() or path[index] == "_"):
            raise ValueError("invalid selector path")
        index += 1
        while index < len(path) and (path[index].isalnum() or path[index] == "_"):
            index += 1
        selectors.append(path[start:index])

        if index < len(path) and path[index] not in ".[":
            raise ValueError("invalid selector path")

    return selectors


def _parse_bracket_selector(path: str, index: int) -> tuple[str | int, int]:
    index += 1
    if index >= len(path):
        raise ValueError("invalid selector path")

    if path[index] == '"':
        end = index + 1
        chars: list[str] = []
        escape = False
        while end < len(path):
            char = path[end]
            if escape:
                chars.append(char)
                escape = False
            elif char == "\\":
                escape = True
            elif char == '"':
                if end + 1 >= len(path) or path[end + 1] != "]":
                    raise ValueError("invalid selector path")
                return "".join(chars), end + 2
            else:
                chars.append(char)
            end += 1
        raise ValueError("invalid selector path")

    end = index
    while end < len(path) and path[end] != "]":
        end += 1
    if end >= len(path):
        raise ValueError("invalid selector path")

    token = path[index:end].strip()
    if not token:
        raise ValueError("invalid selector path")
    if token.lstrip("-").isdigit():
        return int(token), end + 1
    if token[0].isalpha() or token[0] == "_":
        return token, end + 1
    raise ValueError("invalid selector path")


class _TemplateValues(dict[str, Any]):
    def __missing__(self, key: str) -> Any:
        raise KeyError(key)


def _normalize_keys(keys: list[Any]) -> list[str]:
    normalized: list[str] = []
    for key in keys:
        if not isinstance(key, str):
            raise TypeError("keys must be a list of strings")
        normalized.append(key)
    return normalized