"""Parse YAML frontmatter from markdown strings.

Supports arbitrary nesting depth via recursive descent:
  metadata:
    strawpot:
      dependencies:
        skills:
          - git-workflow
        roles:
          - reviewer
  → {"metadata": {"strawpot": {"dependencies": {"skills": ["git-workflow"], "roles": ["reviewer"]}}}}

Python port of convex/lib/frontmatter.ts.
"""

import re


def parse_frontmatter(text: str) -> dict:
    """Parse YAML frontmatter from a markdown string.

    Returns {"frontmatter": {...}, "body": "..."}.
    """
    m = re.match(r"^---\s*\n([\s\S]*?\n)---\s*\n([\s\S]*)$", text)
    if not m:
        return {"frontmatter": {}, "body": text}

    yaml_str = m.group(1)
    body = m.group(2)
    lines = yaml_str.split("\n")

    result, _ = _parse_block(lines, 0, 0)
    return {"frontmatter": result, "body": body}


def _get_indent(line: str) -> int:
    return len(line) - len(line.lstrip())


def _skip_blanks(lines: list[str], start: int) -> int:
    i = start
    while i < len(lines) and not lines[i].strip():
        i += 1
    return i


def _parse_scalar(raw: str):
    """Parse a scalar value: quoted string, boolean, number, or bare string."""
    # Quoted string
    if (raw.startswith('"') and raw.endswith('"')) or (
        raw.startswith("'") and raw.endswith("'")
    ):
        return raw[1:-1]

    # Boolean
    if raw == "true":
        return True
    if raw == "false":
        return False

    # Number
    if re.match(r"^\d+(\.\d+)?$", raw):
        return float(raw) if "." in raw else int(raw)

    # Bare string
    return raw


def _parse_inline_value(raw: str):
    """Parse an inline value (scalar or inline array)."""
    if raw.startswith("[") and raw.endswith("]"):
        return [
            s.strip().strip("\"'")
            for s in raw[1:-1].split(",")
            if s.strip()
        ]
    return _parse_scalar(raw)


def _parse_block(
    lines: list[str], start_idx: int, base_indent: int
) -> tuple[dict, int]:
    """Recursively parse a YAML block at a given indent level.

    Returns (result_dict, next_index).
    """
    result: dict = {}
    i = start_idx

    while i < len(lines):
        line = lines[i]
        trimmed = line.strip()

        if not trimmed:
            i += 1
            continue

        indent = _get_indent(line)
        if indent < base_indent:
            break

        kv = re.match(r"^(\w[\w-]*):\s*(.*)$", trimmed)
        if not kv:
            i += 1
            continue

        key = kv.group(1)
        value = kv.group(2).strip()
        i += 1

        if value:
            result[key] = _parse_inline_value(value)
            continue

        # Empty value — peek to determine array vs nested object
        peek_idx = _skip_blanks(lines, i)

        if peek_idx >= len(lines):
            result[key] = ""
            continue

        peek_indent = _get_indent(lines[peek_idx])
        if peek_indent <= indent:
            result[key] = ""
            continue

        peek_trimmed = lines[peek_idx].strip()

        if peek_trimmed.startswith("- "):
            arr, next_idx = _parse_array(lines, peek_idx, peek_indent)
            result[key] = arr
            i = next_idx
        elif re.match(r"^(\w[\w-]*):\s*(.*)$", peek_trimmed):
            nested, next_idx = _parse_block(lines, peek_idx, peek_indent)
            result[key] = nested
            i = next_idx
        else:
            i = peek_idx + 1

    return result, i


def _parse_array(
    lines: list[str], start_idx: int, base_indent: int
) -> tuple[list[str], int]:
    """Parse consecutive '- item' lines at a given indent level.

    Returns (result_list, next_index).
    """
    result: list[str] = []
    i = start_idx

    while i < len(lines):
        line = lines[i]
        trimmed = line.strip()

        if not trimmed:
            i += 1
            continue

        indent = _get_indent(line)
        if indent < base_indent:
            break
        if not trimmed.startswith("- "):
            break

        val = trimmed[2:].strip()
        # Strip surrounding quotes (consistent with _parse_scalar)
        if (val.startswith('"') and val.endswith('"')) or (
            val.startswith("'") and val.endswith("'")
        ):
            val = val[1:-1]
        result.append(val)
        i += 1

    return result, i


def rewrite_frontmatter_name(text: str, new_name: str) -> str:
    """Rewrite the ``name`` field in YAML frontmatter.

    If the frontmatter contains a ``name:`` line, its value is replaced
    with *new_name*.  If no ``name:`` line exists, one is inserted as the
    first line of the frontmatter block.

    Returns the full text with the updated frontmatter.
    """
    m = re.match(r"^---\s*\n([\s\S]*?\n)---\s*\n([\s\S]*)$", text)
    if not m:
        return text

    yaml_str = m.group(1)
    body = m.group(2)

    if re.search(r"^name:\s*.*$", yaml_str, re.MULTILINE):
        yaml_str = re.sub(
            r"^name:\s*.*$", f"name: {new_name}", yaml_str, count=1, flags=re.MULTILINE
        )
    else:
        yaml_str = f"name: {new_name}\n" + yaml_str

    return f"---\n{yaml_str}---\n{body}"


def extract_dependencies(
    fm: dict, kind: str
) -> dict | None:
    """Extract dependencies from parsed frontmatter.

    Reads from metadata.strawpot.dependencies.
    For skills: returns {"skills": [...]} from a flat array.
    For roles: returns {"skills": [...], "roles": [...]} from a nested object.
    For agents and memories: returns None (no dependency support).
    """
    metadata = fm.get("metadata")
    if not isinstance(metadata, dict):
        return None
    strawpot_meta = metadata.get("strawpot")
    if not isinstance(strawpot_meta, dict):
        return None
    deps = strawpot_meta.get("dependencies")

    if deps is None or kind in ("agent", "memory", "integration"):
        return None

    if kind == "skill" and isinstance(deps, list):
        return {"skills": deps}
    if kind == "role" and isinstance(deps, dict):
        return deps

    return None
