#!/usr/bin/env python3
# chuk_mcp_math/number_theory/figurate_numbers.py
"""
Extended Figurate Numbers - Async Native

Functions for polygonal numbers, centered polygonal numbers, pronic numbers,
and 3D figurate numbers (octahedral, dodecahedral, etc.).

Functions:
- Polygonal numbers: polygonal_number, is_polygonal_number, polygonal_sequence
- Centered polygonal: centered_polygonal, centered_triangular, centered_square
- Pronic numbers: pronic_number, is_pronic_number, pronic_sequence
- Star numbers: star_number, hexagram_number, dodecagram_number
- 3D figurate: octahedral_number, dodecahedral_number, icosahedral_number
- Pyramidal: triangular_pyramidal, square_pyramidal, pentagonal_pyramidal
- Advanced: gnomon_numbers, figurate_number_properties, generating_functions
"""

import math
import asyncio
from typing import List
from chuk_mcp_math.mcp_decorator import mcp_function

# ============================================================================
# GENERAL POLYGONAL NUMBERS
# ============================================================================


@mcp_function(
    description="Calculate the nth s-gonal (polygonal) number.",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {
            "input": {"n": 5, "s": 3},
            "output": 15,
            "description": "5th triangular number",
        },
        {"input": {"n": 4, "s": 4}, "output": 16, "description": "4th square number"},
        {
            "input": {"n": 3, "s": 5},
            "output": 12,
            "description": "3rd pentagonal number",
        },
        {
            "input": {"n": 6, "s": 6},
            "output": 66,
            "description": "6th hexagonal number",
        },
    ],
)
async def polygonal_number(n: int, s: int) -> int:
    """
    Calculate the nth s-gonal number.

    Formula (int): P(s,n) = n * ((s-2)*n - (s-4)) / 2

    Args:
        n (int): Position in the sequence (≥ 0)
        s (int or float): Number of sides of polygon (≥ 3)

    Returns:
        The nth s-gonal number

    Example:
        polygonal_number(5, 3) = 15   # 5th triangular
        polygonal_number(4, 4) = 16   # 4th square
        polygonal_number(3, 5) = 12   # 3rd pentagonal
    """
    if n < 0:
        raise ValueError("Position must be non-negative")
    if s < 3:
        raise ValueError("Polygon must have at least 3 sides")

    if n == 0:
        return 0

    return n * ((s - 2) * n - (s - 4)) // 2


@mcp_function(
    description="Check if a number is an s-gonal number.",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {
            "input": {"num": 15, "s": 3},
            "output": True,
            "description": "15 is triangular",
        },
        {"input": {"num": 16, "s": 4}, "output": True, "description": "16 is square"},
        {
            "input": {"num": 13, "s": 5},
            "output": False,
            "description": "13 is not pentagonal",
        },
        {
            "input": {"num": 28, "s": 6},
            "output": True,
            "description": "28 is hexagonal",
        },
    ],
)
async def is_polygonal_number(num: int, s: int) -> bool:
    """
    Check if a number is an s-gonal number.

    Args:
        num (int or float): Number to test
        s (int or float): Number of sides of polygon (≥ 3)

    Returns:
        True if num is an s-gonal number

    Example:
        is_polygonal_number(15, 3) = True   # 15 is triangular
        is_polygonal_number(16, 4) = True   # 16 is square
        is_polygonal_number(13, 5) = False  # 13 not pentagonal
    """
    if num < 0 or s < 3:
        return False

    if num == 0:
        return True

    # Solve n * ((s-2)*n - (s-4)) / 2 = num
    # (s-2)*n² - (s-4)*n - 2*num = 0
    # Using quadratic formula: n = ((s-4) + sqrt((s-4)² + 8*(s-2)*num)) / (2*(s-2))

    a = s - 2
    b = -(s - 4)
    c = -2 * num

    discriminant = b * b - 4 * a * c

    if discriminant < 0:
        return False

    sqrt_disc = math.sqrt(discriminant)
    n = (-b + sqrt_disc) / (2 * a)

    # Check if n is a positive integer
    if n >= 0 and abs(n - round(n)) < 1e-10:
        n_int = round(n)
        # Verify by computing the polygonal number
        computed = await polygonal_number(n_int, s)
        return computed == num

    return False


@mcp_function(
    description="Generate the first n s-gonal numbers.",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    examples=[
        {
            "input": {"n": 8, "s": 3},
            "output": [0, 1, 3, 6, 10, 15, 21, 28],
            "description": "First 8 triangular numbers",
        },
        {
            "input": {"n": 6, "s": 5},
            "output": [0, 1, 5, 12, 22, 35],
            "description": "First 6 pentagonal numbers",
        },
        {
            "input": {"n": 5, "s": 6},
            "output": [0, 1, 6, 15, 28],
            "description": "First 5 hexagonal numbers",
        },
    ],
)
async def polygonal_sequence(n: int, s: int) -> List[int]:
    """
    Generate the first n s-gonal numbers.

    Args:
        n (int or float): Number of terms to generate
        s (int or float): Number of sides of polygon (≥ 3)

    Returns:
        List of first n s-gonal numbers

    Example:
        polygonal_sequence(8, 3) = [0, 1, 3, 6, 10, 15, 21, 28]
        polygonal_sequence(6, 5) = [0, 1, 5, 12, 22, 35]
    """
    if n <= 0:
        return []
    if s < 3:
        raise ValueError("Polygon must have at least 3 sides")

    sequence = []
    for i in range(n):
        poly_num = await polygonal_number(i, s)
        sequence.append(poly_num)

        # Yield control every 1000 iterations
        if i % 1000 == 0 and n > 1000:
            await asyncio.sleep(0)

    return sequence


# ============================================================================
# CENTERED POLYGONAL NUMBERS
# ============================================================================


@mcp_function(
    description="Calculate the nth centered s-gonal number.",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {
            "input": {"n": 3, "s": 3},
            "output": 10,
            "description": "3rd centered triangular",
        },
        {"input": {"n": 4, "s": 4}, "output": 25, "description": "4th centered square"},
        {
            "input": {"n": 2, "s": 6},
            "output": 7,
            "description": "2nd centered hexagonal",
        },
        {
            "input": {"n": 5, "s": 5},
            "output": 51,
            "description": "5th centered pentagonal",
        },
    ],
)
async def centered_polygonal_number(n: int, s: int) -> int:
    """
    Calculate the nth centered s-gonal number.

    Formula (int): C(s,n) = 1 + s*n*(n-1)/2 for n ≥ 1, C(s,0) = 1

    Args:
        n (int): Position in the sequence (≥ 0)
        s (int or float): Number of sides of polygon (≥ 3)

    Returns:
        The nth centered s-gonal number

    Example:
        centered_polygonal_number(3, 3) = 10  # 3rd centered triangular
        centered_polygonal_number(4, 4) = 25  # 4th centered square
    """
    if n < 0:
        raise ValueError("Position must be non-negative")
    if s < 3:
        raise ValueError("Polygon must have at least 3 sides")

    if n == 0:
        return 1

    return 1 + s * n * (n - 1) // 2


@mcp_function(
    description="Calculate the nth centered triangular number.",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {"input": {"n": 0}, "output": 1, "description": "0th centered triangular"},
        {"input": {"n": 3}, "output": 10, "description": "3rd centered triangular"},
        {"input": {"n": 5}, "output": 16, "description": "5th centered triangular"},
        {"input": {"n": 10}, "output": 31, "description": "10th centered triangular"},
    ],
)
async def centered_triangular_number(n: int) -> int:
    """
    Calculate the nth centered triangular number.

    Formula (int): CT_n = (3*n² + 3*n + 2) / 2 = 1 + 3*T_{n-1} where T_k is kth triangular
    Sequence: 1, 4, 10, 19, 31, 46, 64, 85, 109, 136, ...

    Args:
        n (int): Non-negative integer index

    Returns:
        The nth centered triangular number

    Example:
        centered_triangular_number(0) = 1   # Single center point
        centered_triangular_number(3) = 10  # Center + 3 triangular layers
    """
    if n < 0:
        raise ValueError("Index must be non-negative")

    return (3 * n * n + 3 * n + 2) // 2


@mcp_function(
    description="Calculate the nth centered square number.",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {"input": {"n": 0}, "output": 1, "description": "0th centered square"},
        {"input": {"n": 3}, "output": 13, "description": "3rd centered square"},
        {"input": {"n": 5}, "output": 21, "description": "5th centered square"},
        {"input": {"n": 10}, "output": 41, "description": "10th centered square"},
    ],
)
async def centered_square_number(n: int) -> int:
    """
    Calculate the nth centered square number.

    Formula (int): CS_n = 2*n² + 2*n + 1 = 2*n*(n+1) + 1
    Sequence: 1, 5, 13, 25, 41, 61, 85, 113, 145, 181, ...

    Args:
        n (int): Non-negative integer index

    Returns:
        The nth centered square number

    Example:
        centered_square_number(0) = 1   # Single center point
        centered_square_number(3) = 25  # Center + 3 square layers
    """
    if n < 0:
        raise ValueError("Index must be non-negative")

    return 2 * n * (n + 1) + 1


@mcp_function(
    description="Calculate the nth centered hexagonal number.",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {"input": {"n": 0}, "output": 1, "description": "0th centered hexagonal"},
        {"input": {"n": 2}, "output": 7, "description": "2nd centered hexagonal"},
        {"input": {"n": 4}, "output": 19, "description": "4th centered hexagonal"},
        {"input": {"n": 6}, "output": 37, "description": "6th centered hexagonal"},
    ],
)
async def centered_hexagonal_number(n: int) -> int:
    """
    Calculate the nth centered hexagonal number.

    Formula (int): CH_n = 3*n² + 3*n + 1 = 3*n*(n+1) + 1
    Sequence: 1, 7, 19, 37, 61, 91, 127, 169, 217, 271, ...

    Args:
        n (int): Non-negative integer index

    Returns:
        The nth centered hexagonal number

    Example:
        centered_hexagonal_number(0) = 1   # Single center point
        centered_hexagonal_number(2) = 7   # Center + 2 hexagonal layers
    """
    if n < 0:
        raise ValueError("Index must be non-negative")

    return 3 * n * (n + 1) + 1


# ============================================================================
# PRONIC NUMBERS
# ============================================================================


@mcp_function(
    description="Calculate the nth pronic number (oblong number).",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {"input": {"n": 0}, "output": 0, "description": "0th pronic number"},
        {"input": {"n": 3}, "output": 12, "description": "3rd pronic number"},
        {"input": {"n": 5}, "output": 30, "description": "5th pronic number"},
        {"input": {"n": 10}, "output": 110, "description": "10th pronic number"},
    ],
)
async def pronic_number(n: int) -> int:
    """
    Calculate the nth pronic number (also called oblong number).

    Formula (int): P_n = n * (n + 1)
    Pronic numbers are the product of two consecutive integers.
    Sequence: 0, 2, 6, 12, 20, 30, 42, 56, 72, 90, 110, ...

    Args:
        n (int): Non-negative integer index

    Returns:
        The nth pronic number

    Example:
        pronic_number(0) = 0    # 0 * 1 = 0
        pronic_number(3) = 12   # 3 * 4 = 12
        pronic_number(10) = 110 # 10 * 11 = 110
    """
    if n < 0:
        raise ValueError("Index must be non-negative")

    return n * (n + 1)


@mcp_function(
    description="Check if a number is a pronic number.",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {"input": {"num": 12}, "output": True, "description": "12 = 3 * 4 is pronic"},
        {"input": {"num": 30}, "output": True, "description": "30 = 5 * 6 is pronic"},
        {"input": {"num": 15}, "output": False, "description": "15 is not pronic"},
        {"input": {"num": 0}, "output": True, "description": "0 = 0 * 1 is pronic"},
    ],
)
async def is_pronic_number(num: int) -> bool:
    """
    Check if a number is a pronic number.

    Args:
        num (int): Non-negative integer to test

    Returns:
        True if num is a pronic number

    Example:
        is_pronic_number(12) = True   # 12 = 3 * 4
        is_pronic_number(30) = True   # 30 = 5 * 6
        is_pronic_number(15) = False  # No consecutive integers multiply to 15
    """
    if num < 0:
        return False

    if num == 0:
        return True

    # Solve n*(n+1) = num
    # n² + n - num = 0
    # n = (-1 + sqrt(1 + 4*num)) / 2

    discriminant = 1 + 4 * num
    sqrt_disc = math.sqrt(discriminant)
    n = (-1 + sqrt_disc) / 2

    # Check if n is a non-negative integer
    if n >= 0 and abs(n - round(n)) < 1e-10:
        n_int = round(n)
        return n_int * (n_int + 1) == num

    return False


@mcp_function(
    description="Generate the first n pronic numbers.",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    examples=[
        {
            "input": {"n": 10},
            "output": [0, 2, 6, 12, 20, 30, 42, 56, 72, 90],
            "description": "First 10 pronic numbers",
        },
        {
            "input": {"n": 5},
            "output": [0, 2, 6, 12, 20],
            "description": "First 5 pronic numbers",
        },
    ],
)
async def pronic_sequence(n: int) -> List[int]:
    """
    Generate the first n pronic numbers.

    Args:
        n (int or float): Number of pronic numbers to generate

    Returns:
        List of first n pronic numbers

    Example:
        pronic_sequence(10) = [0, 2, 6, 12, 20, 30, 42, 56, 72, 90]
        pronic_sequence(5) = [0, 2, 6, 12, 20]
    """
    if n <= 0:
        return []

    sequence = []
    for i in range(n):
        sequence.append(i * (i + 1))

        # Yield control every 1000 iterations
        if i % 1000 == 0 and n > 1000:
            await asyncio.sleep(0)

    return sequence


# ============================================================================
# STAR NUMBERS
# ============================================================================


@mcp_function(
    description="Calculate the nth star number (6-pointed star).",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {"input": {"n": 1}, "output": 1, "description": "1st star number"},
        {"input": {"n": 2}, "output": 13, "description": "2nd star number"},
        {"input": {"n": 3}, "output": 37, "description": "3rd star number"},
        {"input": {"n": 4}, "output": 73, "description": "4th star number"},
    ],
)
async def star_number(n: int) -> int:
    """
    Calculate the nth star number (6-pointed star).

    Formula (int): S_n = 6*n*(n-1) + 1
    Star numbers represent centered hexagrams (6-pointed stars).
    Sequence: 1, 13, 37, 73, 121, 181, 253, 337, 433, 541, ...

    Args:
        n (int): Positive integer index (≥ 1)

    Returns:
        The nth star number

    Example:
        star_number(1) = 1    # Single central point
        star_number(2) = 13   # Central point + first star layer
        star_number(3) = 37   # Central point + two star layers
    """
    if n < 1:
        raise ValueError("Star number index must be positive")

    return 6 * n * (n - 1) + 1


@mcp_function(
    description="Calculate the nth hexagram number (Star of David).",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {"input": {"n": 1}, "output": 1, "description": "1st hexagram number"},
        {"input": {"n": 2}, "output": 13, "description": "2nd hexagram number"},
        {"input": {"n": 3}, "output": 37, "description": "3rd hexagram number"},
    ],
)
async def hexagram_number(n: int) -> int:
    """
    Calculate the nth hexagram number (same as star number).

    Hexagram numbers count the dots in nested hexagrams (Stars of David).

    Args:
        n (int): Positive integer index (≥ 1)

    Returns:
        The nth hexagram number
    """
    return await star_number(n)


# ============================================================================
# 3D FIGURATE NUMBERS
# ============================================================================


@mcp_function(
    description="Calculate the nth octahedral number.",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {"input": {"n": 1}, "output": 1, "description": "1st octahedral number"},
        {"input": {"n": 2}, "output": 6, "description": "2nd octahedral number"},
        {"input": {"n": 3}, "output": 19, "description": "3rd octahedral number"},
        {"input": {"n": 4}, "output": 44, "description": "4th octahedral number"},
    ],
)
async def octahedral_number(n: int) -> int:
    """
    Calculate the nth octahedral number.

    Formula (int): O_n = (2*n³ + n) / 3 = n*(2*n² + 1) / 3
    Octahedral numbers count spheres in octahedral arrangements.
    Sequence: 1, 6, 19, 44, 85, 146, 231, 344, 489, 670, ...

    Args:
        n (int): Positive integer index (≥ 1)

    Returns:
        The nth octahedral number

    Example:
        octahedral_number(1) = 1    # Single sphere
        octahedral_number(2) = 6    # Central + 6 in octahedral shell
        octahedral_number(3) = 19   # Previous + second octahedral shell
    """
    if n < 1:
        raise ValueError("Octahedral number index must be positive")

    return n * (2 * n * n + 1) // 3


@mcp_function(
    description="Calculate the nth dodecahedral number.",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {"input": {"n": 1}, "output": 1, "description": "1st dodecahedral number"},
        {"input": {"n": 2}, "output": 20, "description": "2nd dodecahedral number"},
        {"input": {"n": 3}, "output": 84, "description": "3rd dodecahedral number"},
        {"input": {"n": 4}, "output": 220, "description": "4th dodecahedral number"},
    ],
)
async def dodecahedral_number(n: int) -> int:
    """
    Calculate the nth dodecahedral number.

    Formula (int): D_n = n * (3*n - 1) * (3*n - 2) / 2
    Dodecahedral numbers are 3D analogs of pentagonal numbers.
    Sequence: 1, 20, 84, 220, 455, 816, 1330, 2024, 2925, 4060, ...

    Args:
        n (int): Positive integer index (≥ 1)

    Returns:
        The nth dodecahedral number

    Example:
        dodecahedral_number(1) = 1     # Single point
        dodecahedral_number(2) = 20    # Dodecahedral shell
        dodecahedral_number(3) = 84    # Two dodecahedral shells
    """
    if n < 1:
        raise ValueError("Dodecahedral number index must be positive")

    return n * (3 * n - 1) * (3 * n - 2) // 2


@mcp_function(
    description="Calculate the nth icosahedral number.",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {"input": {"n": 1}, "output": 1, "description": "1st icosahedral number"},
        {"input": {"n": 2}, "output": 12, "description": "2nd icosahedral number"},
        {"input": {"n": 3}, "output": 48, "description": "3rd icosahedral number"},
        {"input": {"n": 4}, "output": 124, "description": "4th icosahedral number"},
    ],
)
async def icosahedral_number(n: int) -> int:
    """
    Calculate the nth icosahedral number.

    Formula (int): I_n = n * (5*n² - 5*n + 2) / 2
    Icosahedral numbers count spheres arranged in icosahedral patterns.
    Sequence: 1, 12, 48, 124, 255, 456, 742, 1128, 1629, 2260, ...

    Args:
        n (int): Positive integer index (≥ 1)

    Returns:
        The nth icosahedral number

    Example:
        icosahedral_number(1) = 1     # Single sphere
        icosahedral_number(2) = 12    # Central + icosahedral shell
        icosahedral_number(3) = 48    # Previous + second shell
    """
    if n < 1:
        raise ValueError("Icosahedral number index must be positive")

    return n * (5 * n * n - 5 * n + 2) // 2


# ============================================================================
# PYRAMIDAL NUMBERS
# ============================================================================


@mcp_function(
    description="Calculate the nth triangular pyramidal number (tetrahedral).",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {"input": {"n": 1}, "output": 1, "description": "1st tetrahedral number"},
        {"input": {"n": 4}, "output": 20, "description": "4th tetrahedral number"},
        {"input": {"n": 5}, "output": 35, "description": "5th tetrahedral number"},
        {"input": {"n": 10}, "output": 220, "description": "10th tetrahedral number"},
    ],
)
async def triangular_pyramidal_number(n: int) -> int:
    """
    Calculate the nth triangular pyramidal (tetrahedral) number.

    Formula (int): Tet_n = n * (n + 1) * (n + 2) / 6
    Sum of first n triangular numbers.
    Sequence: 1, 4, 10, 20, 35, 56, 84, 120, 165, 220, ...

    Args:
        n (int): Positive integer index (≥ 1)

    Returns:
        The nth tetrahedral number

    Example:
        triangular_pyramidal_number(1) = 1    # Single sphere
        triangular_pyramidal_number(4) = 20   # 4-layer triangular pyramid
        triangular_pyramidal_number(10) = 220 # 10-layer pyramid
    """
    if n < 1:
        raise ValueError("Tetrahedral number index must be positive")

    return n * (n + 1) * (n + 2) // 6


@mcp_function(
    description="Calculate the nth square pyramidal number.",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {"input": {"n": 1}, "output": 1, "description": "1st square pyramidal"},
        {"input": {"n": 4}, "output": 30, "description": "4th square pyramidal"},
        {"input": {"n": 5}, "output": 55, "description": "5th square pyramidal"},
        {"input": {"n": 10}, "output": 385, "description": "10th square pyramidal"},
    ],
)
async def square_pyramidal_number(n: int) -> int:
    """
    Calculate the nth square pyramidal number.

    Formula (int): SP_n = n * (n + 1) * (2*n + 1) / 6
    Sum of first n square numbers.
    Sequence: 1, 5, 14, 30, 55, 91, 140, 204, 285, 385, ...

    Args:
        n (int): Positive integer index (≥ 1)

    Returns:
        The nth square pyramidal number

    Example:
        square_pyramidal_number(1) = 1    # Single cube
        square_pyramidal_number(4) = 30   # 4-layer square pyramid
        square_pyramidal_number(10) = 385 # 10-layer pyramid
    """
    if n < 1:
        raise ValueError("Square pyramidal number index must be positive")

    return n * (n + 1) * (2 * n + 1) // 6


@mcp_function(
    description="Calculate the nth pentagonal pyramidal number.",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    cache_strategy="memory",
    examples=[
        {"input": {"n": 1}, "output": 1, "description": "1st pentagonal pyramidal"},
        {"input": {"n": 3}, "output": 18, "description": "3rd pentagonal pyramidal"},
        {"input": {"n": 5}, "output": 70, "description": "5th pentagonal pyramidal"},
        {"input": {"n": 8}, "output": 240, "description": "8th pentagonal pyramidal"},
    ],
)
async def pentagonal_pyramidal_number(n: int) -> int:
    """
    Calculate the nth pentagonal pyramidal number.

    Formula (int): PP_n = n² * (n + 1) / 2
    Sum of first n pentagonal numbers.
    Sequence: 1, 6, 18, 40, 75, 126, 196, 288, 405, 550, ...

    Args:
        n (int): Positive integer index (≥ 1)

    Returns:
        The nth pentagonal pyramidal number

    Example:
        pentagonal_pyramidal_number(1) = 1    # Single point
        pentagonal_pyramidal_number(3) = 18   # 3-layer pentagonal pyramid
        pentagonal_pyramidal_number(8) = 288  # 8-layer pyramid
    """
    if n < 1:
        raise ValueError("Pentagonal pyramidal number index must be positive")

    return n * n * (n + 1) // 2


# ============================================================================
# ADVANCED FIGURATE NUMBERS
# ============================================================================


@mcp_function(
    description="Calculate gnomon numbers for s-gonal numbers.",
    namespace="arithmetic",
    category="figurate_numbers",
    execution_modes=["local", "remote"],
    examples=[
        {"input": {"n": 3, "s": 4}, "output": 5, "description": "3rd square gnomon"},
        {
            "input": {"n": 4, "s": 3},
            "output": 4,
            "description": "4th triangular gnomon",
        },
        {"input": {"n": 2, "s": 6}, "output": 6, "description": "2nd hexagonal gnomon"},
    ],
)
async def gnomon_number(n: int, s: int) -> int:
    """
    Calculate the nth gnomon for s-gonal numbers.

    A gnomon is the difference between consecutive figurate numbers.
    For s-gonal numbers (int): Gnomon_n = P(s,n) - P(s,n-1)

    Args:
        n (int): Positive integer index (≥ 1)
        s (int or float): Number of sides (≥ 3)

    Returns:
        The nth gnomon for s-gonal numbers

    Example:
        gnomon_number(3, 4) = 5   # 3rd square gnomon
        gnomon_number(4, 3) = 4   # 4th triangular gnomon
    """
    if n < 1:
        raise ValueError("Gnomon index must be positive")
    if s < 3:
        raise ValueError("Polygon must have at least 3 sides")

    poly_n = await polygonal_number(n, s)
    poly_n_minus_1 = await polygonal_number(n - 1, s)

    return poly_n - poly_n_minus_1


# Export all functions
__all__ = [
    # General polygonal numbers
    "polygonal_number",
    "is_polygonal_number",
    "polygonal_sequence",
    # Centered polygonal numbers
    "centered_polygonal_number",
    "centered_triangular_number",
    "centered_square_number",
    "centered_hexagonal_number",
    # Pronic numbers
    "pronic_number",
    "is_pronic_number",
    "pronic_sequence",
    # Star numbers
    "star_number",
    "hexagram_number",
    # 3D figurate numbers
    "octahedral_number",
    "dodecahedral_number",
    "icosahedral_number",
    # Pyramidal numbers
    "triangular_pyramidal_number",
    "square_pyramidal_number",
    "pentagonal_pyramidal_number",
    # Advanced
    "gnomon_number",
]
