"""
autobee.dp — Classic dynamic programming algorithms.
"""


def fibonacci(n: int) -> dict:
    """
    Fibonacci — computes the nth Fibonacci number using bottom-up DP.

    Args:
        n: Non-negative integer.

    Returns:
        dict with keys: result (F(n)), steps (all values 0..n), algo.

    Example:
        >>> from autobee import fibonacci
        >>> fibonacci(10)["result"]
        55
    """
    if n < 0:
        raise ValueError("n must be a non-negative integer.")
    if n == 0:
        return {"result": 0, "steps": [0], "algo": "Fibonacci",
                "complexity": {"time": "O(n)", "space": "O(n)"}}
    dp = [0, 1]
    for i in range(2, n + 1):
        dp.append(dp[i - 1] + dp[i - 2])
    return {
        "result": dp[n],
        "steps": dp,
        "algo": "Fibonacci",
        "complexity": {"time": "O(n)", "space": "O(n)"},
    }


def knapsack(weights: list, values: list, capacity: int) -> dict:
    """
    0/1 Knapsack — maximises total value without exceeding weight capacity.

    Args:
        weights:  List of item weights (ints).
        values:   List of item values (ints).
        capacity: Maximum weight capacity.

    Returns:
        dict with keys: result (max value), dp_table, algo.

    Example:
        >>> from autobee import knapsack
        >>> knapsack([1, 3, 4, 5], [1, 4, 5, 7], 7)["result"]
        9
    """
    if len(weights) != len(values):
        raise ValueError("weights and values must have the same length.")
    n = len(weights)
    dp = [[0] * (capacity + 1) for _ in range(n + 1)]
    for i in range(1, n + 1):
        for w in range(capacity + 1):
            dp[i][w] = dp[i - 1][w]
            if weights[i - 1] <= w:
                dp[i][w] = max(dp[i][w], dp[i - 1][w - weights[i - 1]] + values[i - 1])
    return {
        "result": dp[n][capacity],
        "dp_table": dp,
        "algo": "0/1 Knapsack",
        "complexity": {"time": "O(n × W)", "space": "O(n × W)"},
    }


def lcs(s1: str, s2: str) -> dict:
    """
    Longest Common Subsequence — finds the length of the longest subsequence
    common to both strings.

    Args:
        s1: First string.
        s2: Second string.

    Returns:
        dict with keys: result (length), dp_table, s1, s2, algo.

    Example:
        >>> from autobee import lcs
        >>> lcs("ABCBDAB", "BDCAB")["result"]
        4
    """
    m, n = len(s1), len(s2)
    dp = [[0] * (n + 1) for _ in range(m + 1)]
    for i in range(1, m + 1):
        for j in range(1, n + 1):
            if s1[i - 1] == s2[j - 1]:
                dp[i][j] = dp[i - 1][j - 1] + 1
            else:
                dp[i][j] = max(dp[i - 1][j], dp[i][j - 1])
    return {
        "result": dp[m][n],
        "dp_table": dp,
        "s1": s1,
        "s2": s2,
        "algo": "LCS",
        "complexity": {"time": "O(m × n)", "space": "O(m × n)"},
    }
