"""
Vedic Mathematics – Complete Implementation of All 16 Sutras with GIF Visualization
-------------------------------------------------------------------------------

This module provides a full Python implementation of all sixteen classical
Vedic Mathematics Sutras along with their corresponding GIF-based visual
explanations. The purpose of this project is to demonstrate how ancient
Vedic mathematical techniques can be represented, automated, and visualized
using modern Python programming.

The project implements:

    • 16 private Sutra functions:
        Each Sutra is implemented as a private (@staticmethod) method inside
        the VedicSutraGIF class. These functions encapsulate the mathematical
        logic behind the Sutra but are not intended for direct public use.

    • 16 public GIF generation functions:
        For every Sutra, there exists a corresponding make_gif_<sutra>()
        method. These functions:
            - Take user input
            - Call the private Sutra method for computation
            - Render step-by-step solution frames using matplotlib
            - Export the steps as an animated GIF for easier understanding

Project Objectives:
    1. Demonstrate the computational power of Vedic Mathematics in Python.
    2. Provide visually intuitive explanations using animation.
    3. Offer a clean and modular class-based design suitable for packaging,
       code reuse, and PyPI distribution.
    4. Enable students and educators to explore and teach Vedic Sutras with
       clear procedural breakdowns.

Structure:
    class VedicSutraGIF:
        - Contains private Sutra methods (prefixed with “_”)
        - Contains public GIF functions (make_gif_<sutra>())
        - Uses matplotlib.animation to generate high-quality GIFs

Module Features:
    • Ekadhikena Purvena (Squaring numbers ending in 5)
    • Nikhilam (Multiplication near powers of 10)
    • Urdhva-Tiryagbhyam (Vertical and crosswise multiplication)
    • Paravartya Yojayet (Vedic division)
    • Shunyam Samyasamuccaye (Equal sums sutra)
    • Anurupye Shunyam Anyat (Proportional elimination)
    • Sankalana-Vyavakalanabhyam (Add/subtract method)
    • Puranapuranabhyam (Completion / non-completion)
    • Chalana-Kalanabhyam (Quadratic solving)
    • Yaavadunam (Squaring near bases)
    • Vyashtisamashti (Part-whole multiplication)
    • Shesanyankena Charamena (Remainder method)
    • Sopantyadvayamantyam (Divisibility by 11)
    • Ekanyunena Purvena (One less than previous)
    • Gunitasamuccayah (Product-sum ratio sutra)
    • Gunakasamuccayah (Quadratic factorization)

Intended Use:
    Users should call only the public GIF functions. The underlying Sutra
    logic remains private to maintain clean API design and prevent misuse.

Educational Purpose:
    This project serves as a practical demonstration for students, teachers,
    and developers looking to explore Vedic Mathematics through animation.
    It is suitable for academic presentations, Python teaching modules,
    mathematical visualization, and eventual publication on PyPI.

Author:
    Lakshya Khatri
"""


import matplotlib.pyplot as plt
from matplotlib.animation import FuncAnimation, PillowWriter
from typing import Optional

class VedicSutraGIF:
    """Vedic Mathematics Package with step-by-step GIF visualization."""
    
    # 1️ Ekadhikena Purvena
    @staticmethod
    def _ekadhikena_purvena(n: int):
        """
        Ekadhikena Purvena (By One More Than the Previous)

        This sutra is used specifically to square numbers ending in 5.
        The number is split into:
            - The left part (all digits except the last 5)
            - The right part (always "25")

        Algorithm:
            1. Let n = 10a + 5
            2. Compute L = a × (a + 1)
            3. Result = concatenate(L, "25")

        Example:
            35²:
                a = 3
                L = 3 × 4 = 12
                Result = 1225

        Parameters:
            n (int): Any positive integer that ends with digit 5.

        Returns:
            int: The exact square of the given number.

        Raises:
            ValueError: If the number does not end with digit 5.
        """

        if n % 10 != 5:
            raise ValueError("Number must end with 5")
        base = n // 10
        prod = base * (base + 1)
        result = prod * 100 + 25
        return base, prod, result

    # 2️ Nikhilam Navataścaramam Daśatah
    @staticmethod
    def _nikhilam_navatascaramam_dashatah(a: int, b: int):
        """
        Nikhilam Navatascaramam Dasatah (All From 9 and Last From 10)

        This sutra provides a fast multiplication technique for numbers
        that are close to a power of 10 (10, 100, 1000 …).

        Algorithm:
            1. Choose base = nearest power of 10.
            2. Compute deficiencies:
                   da = a − base
                   db = b − base
            3. Compute:
                   Left  = a + db
                   Right = da × db
            4. Concatenate Left and Right parts to get final product.

        Example:
            98 × 97:
                base = 100
                da = -2, db = -3
                Left  = 98 - 3 = 95
                Right = 6 → padded to 2 digits → 06
                Result = 9506

        Parameters:
            a (int): First number.
            b (int): Second number.

        Returns:
            int: Exact product using Nikhilam method.
        """
        
        if len(str(a)) != len(str(b)):
            raise ValueError("Numbers must have same number of digits for this simple routine")

        d = len(str(a))
        base = 10 ** d
        diff_a = a - base
        diff_b = b - base
        left = a + diff_b
        right = diff_a * diff_b
        result = left * base + right
        carry, right_mod = divmod(right, base)
        display_left = left + carry
        right_str = str(abs(right_mod)).zfill(d)
        return base, diff_a, diff_b, left, right, display_left, right_str, result

    # 3️ Urdhva–Tiryagbhyam
    @staticmethod
    def _urdhva_tiryagbhyam(a: int, b: int):
        """
        Urdhva–Tiryagbhyam (Vertical and Crosswise Multiplication)

        A general-purpose multiplication algorithm that works digit-by-digit
        through vertical and crosswise operations.

        This method reduces multi-digit multiplication to a series of smaller
        digit multiplications + carry handling.

        Steps:
            - Multiply digits vertically (same-position).
            - Multiply digits crosswise.
            - Add all products and propagate carries.
            - Construct final number.

        Works with ANY number of digits.

        Parameters:
            a (int): First integer.
            b (int): Second integer.

        Returns:
            int: Product computed using vertical–crosswise method.
        """

        a_str, b_str = str(a)[::-1], str(b)[::-1]
        n, m = len(a_str), len(b_str)
        result = [0] * (n + m)

        for i in range(n):
            for j in range(m):
                result[i + j] += int(a_str[i]) * int(b_str[j])

        # Handle carry
        for k in range(len(result)):
            if result[k] >= 10:
                carry = result[k] // 10
                result[k] %= 10
                if k + 1 < len(result):
                    result[k + 1] += carry

        return a, b, ''.join(map(str, result[::-1])), int(''.join(map(str, result[::-1])))

    # 4️ Parāvartya Yojayet
    @staticmethod
    def _paravartya_yojayet(dividend: int, divisor: int):
        """
        Paravartya Yojayet (Transpose and Apply)

        Implements long division using only repeated subtraction,
        avoiding Python's // and % operators, closely reflecting the
        classical Vedic procedure.

        Algorithm:
            - Perform digit-by-digit division.
            - At each step, bring down next digit.
            - Repeatedly subtract divisor until remainder < divisor.
            - Construct quotient one digit at a time.

        Parameters:
            dividend (int): The number to be divided.
            divisor (int): The number to divide by (non-zero).

        Returns:
            tuple:
                (quotient: int, remainder: int)

        Raises:
            ZeroDivisionError: If divisor is 0.
        """
        if divisor == 0:
            raise ZeroDivisionError("Divisor cannot be zero")

        d_len = len(str(divisor))
        base = 10 ** (d_len - 1)
        deviation = divisor - base  
        steps = []

        steps.append(f"Dividing {dividend} by {divisor}")
        steps.append(f"Base = {base}, Deviation = {divisor} − {base} = {deviation}")

        quotient_digits = []
        remainder = 0
        num_str = str(dividend)

        for i, ch in enumerate(num_str):
            current = remainder * 10 + int(ch)
            q_digit = current // divisor
            remainder = current % divisor
            quotient_digits.append(str(q_digit))
            steps.append(
                f"Step {i+1}: Bring down {ch}, Current = {current}, "
                f"Quotient digit = {q_digit}, Remainder = {remainder}"
            )

        quotient = int("".join(quotient_digits))
        steps.append(f"Final Quotient = {quotient}, Remainder = {remainder}")
        return dividend, divisor, quotient, remainder, steps
    
    #5 Shunyam Saamyasamuccaye
    @staticmethod
    def _shunyam_samyasamuccaye(expr1: tuple, expr2: tuple):
        """
        Shunyam Samyasamuccaye (When the Sum is the Same, It is Zero)

        Used to solve rational equations of the form:
            (x + a) / (x + b) = (x + c) / (x + d)

        Key rule:
            If (a + d) = (b + c), then x = 0 immediately.

        Otherwise:
            Solve using cross-multiplication and simplification.

        Parameters:
            a, b, c, d (float): Constants in the rational equation.

        Returns:
            float or str:
                - The value of x
                - 0.0 for equal-sum case
                - "infinite_solutions"
                - "no_solution"
        """
        a, b = expr1
        c, d = expr2

        # Calculate helper values
        sum_left = a + d
        sum_right = b + c
        ad, bc = a * d, b * c

        # Case 1: Sums not equal -> Regular case
        if sum_left != sum_right:
            numerator = bc - ad
            denominator = sum_left - sum_right
            x = numerator / denominator
            return {
                "type": "unique",
                "a": a, "b": b, "c": c, "d": d,
                "sum_left": sum_left,
                "sum_right": sum_right,
                "ad": ad, "bc": bc,
                "numerator": numerator,
                "denominator": denominator,
                "x": x,
                "message": f"Unique solution: x = {round(x, 3)}"
            }

        # Case 2: Equal sums
        else:
            if ad == bc:
                return {
                    "type": "infinite",
                    "a": a, "b": b, "c": c, "d": d,
                    "sum_left": sum_left,
                    "sum_right": sum_right,
                    "ad": ad, "bc": bc,
                    "message": "Equal sums and proportional → Infinite solutions (true for all x)."
                }
            else:
                return {
                    "type": "none",
                    "a": a, "b": b, "c": c, "d": d,
                    "sum_left": sum_left,
                    "sum_right": sum_right,
                    "ad": ad, "bc": bc,
                    "message": "Equal sums but not proportional → No valid solution."
                }
            
    #6 Anurupye Shunyamanyat    
    @staticmethod
    def _anurupye_shunyam_anyat(eq1: tuple, eq2: tuple):
        """
        Anurupye Shunyam Anyat (Proportion Leads to Zero)

        Used for solving simultaneous linear equations:
            a1*x + b1*y = c1
            a2*x + b2*y = c2

        Key idea:
            - If coefficients are in equal ratio → one variable is 0.
            - Otherwise solve using determinant method.

        Parameters:
            a1, b1, c1 : coefficients of first equation.
            a2, b2, c2 : coefficients of second equation.

        Returns:
            tuple or str:
                - (x, y) : real solution
                - "infinite_solutions"
                - "no_solution"
        """
        a1, b1, c1 = eq1
        a2, b2, c2 = eq2

        # Compute ratios (avoid zero division)
        ra = (a1 / a2) if a2 != 0 else None
        rb = (b1 / b2) if b2 != 0 else None
        rc = (c1 / c2) if c2 != 0 else None

        # -----------------------
        # CASE 1: y = 0
        # -----------------------
        if ra is not None and ra == rc:
            y = 0
            if a1 == 0:
                return {"type": "none", "message": "Cannot solve for x when a1=0"}
            x = c1 / a1
            return {
                "type": "y_zero",
                "x": x,
                "y": y,
                "message": f"y = 0, x = {x}"
            }

        # -----------------------
        # CASE 2: x = 0
        # -----------------------
        if rb is not None and rb == rc:
            x = 0
            if b1 == 0:
                return {"type": "none", "message": "Cannot solve for y when b1=0"}
            y = c1 / b1
            return {
                "type": "x_zero",
                "x": x,
                "y": y,
                "message": f"x = 0, y = {y}"
            }

        # -----------------------
        # CASE 3: General case (determinant)
        # -----------------------
        det = a1 * b2 - b1 * a2
        if det == 0:
            return {"type": "none", "message": "No unique solution (dependent or parallel)."}

        x = (c1 * b2 - b1 * c2) / det
        y = (a1 * c2 - c1 * a2) / det

        return {
            "type": "unique",
            "x": x,
            "y": y,
            "message": f"Unique solution: x = {x}, y = {y}"
        }
    
    #7 Sankalana-vyavakalanabhyam
    @staticmethod
    def _sankalana_vyavakalanabhyam(eq1: tuple, eq2: tuple):
        """
        Sankalana–Vyavakalanabhyam (Addition and Subtraction Method)

        Solves two linear equations by using addition or subtraction
        to eliminate variables.

        Identical to elimination method but structured in Vedic style.

        Parameters:
            a1,b1,c1 : coefficients of first equation.
            a2,b2,c2 : coefficients of second equation.

        Returns:
            tuple or str:
                (x, y) or error flags as strings.
        """
        a1, b1, c1 = eq1
        a2, b2, c2 = eq2

        # Addition (Sankalana)
        A = a1 + a2
        B = b1 + b2
        C = c1 + c2

        # Subtraction (Vyavakalana)
        D = a1 - a2
        E = b1 - b2
        F = c1 - c2

        # Determinant
        det = a1 * b2 - a2 * b1

        # Handle no-unique-solution cases first
        if det == 0:
            if (a1 * c2 == a2 * c1) and (b1 * c2 == b2 * c1):
                return {"type": "infinite", "message": "Infinite solutions (dependent)."}
            else:
                return {"type": "none", "message": "No solution (parallel lines)."}

        route = None

        # -----------------------------
        # CASE 1: Subtraction eliminates y (E = 0)
        # -----------------------------
        if E == 0 and D != 0:
            x = F / D
            route = "subtract_D"
        # -----------------------------
        # CASE 2: Subtraction eliminates x (D = 0)
        # -----------------------------
        elif D == 0 and E != 0:
            y = F / E
            # then compute x
            x = (c1 - b1 * y) / a1
            route = "subtract_E"
        # -----------------------------
        # CASE 3: Addition eliminates y (B = 0)
        # -----------------------------
        elif B == 0 and A != 0:
            x = C / A
            route = "add_A"
        # -----------------------------
        # CASE 4: Addition eliminates x (A = 0)
        # -----------------------------
        elif A == 0 and B != 0:
            y = C / B
            x = (c1 - b1 * y) / a1
            route = "add_B"
        # -----------------------------
        # CASE 5: Fallback → determinant method
        # -----------------------------
        else:
            x = (c1 * b2 - b1 * c2) / det
            y = (a1 * c2 - c1 * a2) / det
            route = "cramer"

        # Compute y if not computed
        if 'y' not in locals():
            y = (c1 - a1 * x) / b1 if b1 != 0 else (c2 - a2 * x) / b2

        return {
            "type": "unique",
            "x": x,
            "y": y,
            "route": route,
            "A": A, "B": B, "C": C,
            "D": D, "E": E, "F": F,
            "message": f"x={x}, y={y}"
        }

    #8 Puranapuranabhyam   
    @staticmethod
    def _puranapuranabhyam(expr: tuple):
        """
        Puranapuranabhyam (Completion or Non-Completion)

        Used to solve equations of type:
            (x + a)/b = c / (x + d)

        Cross-multiplying leads to a quadratic equation:
            (x + a)(x + d) = b*c

        The method then solves the resulting quadratic using the
        discriminant.

        Parameters:
            a, b, c, d (float)

        Returns:
            tuple:
                - Two real roots
                - One repeated root
                - Two complex roots
        """
        a, b, c, d = expr  # unpack

        # Ensure denominator non-zero
        if b == 0:
            return {"type": "error", "message": "Invalid: b cannot be zero."}

        # Build quadratic:
        # (x+a)(x+d) = bc
        # x^2 + (a+d)x + (ad - bc) = 0

        A = 1
        B = a + d
        C = (a * d) - (b * c)

        discriminant = B * B - 4 * A * C

        # Case: two real roots
        if discriminant > 0:
            x1 = (-B + discriminant**0.5) / (2 * A)
            x2 = (-B - discriminant**0.5) / (2 * A)
            return {
                "type": "two_real",
                "a": a, "b": b, "c": c, "d": d,
                "A": A, "B": B, "C": C,
                "discriminant": discriminant,
                "x1": x1, "x2": x2,
                "message": f"Two real solutions: {x1} and {x2}"
            }

        # Case: one root
        elif discriminant == 0:
            x = -B / (2 * A)
            return {
                "type": "one_real",
                "a": a, "b": b, "c": c, "d": d,
                "A": A, "B": B, "C": C,
                "discriminant": discriminant,
                "x": x,
                "message": f"Single repeated root: {x}"
            }

        # Case: complex
        else:
            real = -B / (2 * A)
            imag = (abs(discriminant)**0.5) / (2 * A)
            return {
                "type": "complex",
                "a": a, "b": b, "c": c, "d": d,
                "A": A, "B": B, "C": C,
                "discriminant": discriminant,
                "real": real,
                "imag": imag,
                "message": f"Complex roots: {real} ± {imag}i"
            }
        
    #9 Chalana-Kalanabyham    
    @staticmethod
    def _chalana_kalanabhyam(a: float, b: float, c: float):
        """
        Chalana–Kalanabhyam (Differential Calculus Approach)

        Solves a quadratic equation:
            a*x^2 + b*x + c = 0

        Uses the classical discriminant method:
            D = b^2 - 4ac

        Decision:
            - D > 0 → two real roots
            - D = 0 → repeated root
            - D < 0 → complex roots

        Parameters:
            a, b, c (float): Quadratic coefficients.

        Returns:
            tuple: root(s) depending on discriminant.
        """
        # Case: a = 0 → Linear equation
        if a == 0:
            if b == 0:
                return {"type": "invalid", "message": "Not an equation (a = b = 0)."}
            x = -c / b
            return {"type": "linear", "x": x, "message": f"Linear root: x = {x}"}

        # Quadratic discriminant
        D = b * b - 4 * a * c

        # Case: Two real roots
        if D > 0:
            x1 = (-b + D**0.5) / (2 * a)
            x2 = (-b - D**0.5) / (2 * a)
            return {
                "type": "two_real",
                "D": D,
                "x1": x1,
                "x2": x2,
                "message": f"Two real solutions: {x1}, {x2}"
            }

        # Case: One real root
        elif D == 0:
            x = -b / (2 * a)
            return {
                "type": "one_real",
                "D": D,
                "x": x,
                "message": f"One repeated real root: {x}"
            }

        # Case: Complex roots
        else:
            real = -b / (2 * a)
            imag = (abs(D)**0.5) / (2 * a)
            return {
                "type": "complex",
                "D": D,
                "real": real,
                "imag": imag,
                "message": f"Complex roots: {real} ± {imag}i"
            }

    #10 Yaavadunam 
    @staticmethod
    def _yaavadunam(n: int, base: Optional[int] = None) -> dict:
        """
        Yaavadunam (Whatever the Deficiency)

        Used for squaring numbers close to a base (10, 100, 1000...).

        Algorithm:
            Let n = base - d
            Then:
                n^2 = (base - d)^2
                    = base*(base - 2d) + d^2
            Build the square as:
                Left  = n - d
                Right = d^2 (padded)

        Auto-detects base if not provided.

        Fallback:
            If number is not sufficiently close to base,
            falls back to Urdhva-like digit multiplication.

        Parameters:
            n (int): Number to square.
            base (int, optional): Power of 10.

        Returns:
            int: Square of n.
        """
        if base is None:
            # auto-detect base: choose between 10^(d-1) and 10^d whichever is closer
            if n == 0:
                base = 10
            else:
                digits = len(str(abs(n)))
                lower = 10 ** (digits - 1)
                upper = 10 ** digits
                # pick the closer base (tie -> lower)
                if abs(n - lower) <= abs(upper - n):
                    base = lower
                else:
                    base = upper

        # compute signed difference e = N - base
        e = n - base  # positive if above base, negative if below
        right_num = e * e  # numeric right part
        pad = len(str(base)) - 1  # number of zeros in base
        left = 2 * n - base  # equals base + 2e

        # final numeric result (handles carries when right_num >= 10**pad)
        result_num = left * (10 ** pad) + right_num

        return {
            "n": n,
            "base": base,
            "e": e,
            "left": left,
            "right_num": right_num,
            "pad": pad,
            "result": result_num,
            # helpful formatted strings
            "left_str": str(left),
            "right_str": str(right_num).zfill(pad),
            "message": f"{n}^2 = {result_num}"
        }

    #11 Vyashtisamasthi 
    @staticmethod
    def _vyashtisamashti(a: int, b: int):
        """
        Vyashtisamashti (Part–Whole Method)

        A fast multiplication trick:

            (a + b)(c + d)

        Instead of manually multiplying, this sutra emphasizes
        grouping (samasti) and individual parts (vyashti).

        Parameters:
            a, b, c, d (int)

        Returns:
            int: (a + b) * (c + d)
        """
        # Convert to positive but keep sign separately
        sign = 1
        if a < 0:
            a = -a
            sign *= -1
        if b < 0:
            b = -b
            sign *= -1

        # Auto-split each number into (higher part, lower part)
        # For simplicity we use tens-units splitting
        A, a_low = divmod(a, 10)
        B, b_low = divmod(b, 10)

        # Compute the parts
        AB = A * B * 100
        Ab = A * b_low * 10
        aB = a_low * B * 10
        ab = a_low * b_low

        result = sign * (AB + Ab + aB + ab)

        return {
            "A": A, "a": a_low,
            "B": B, "b": b_low,
            "AB": AB, "Ab": Ab,
            "aB": aB, "ab": ab,
            "result": result,
            "message": f"{result}"
        }

    #12 Shesanyankena Charamena 
    @staticmethod
    def _shesanyankena_charamena(n: int, d: int):
        """
        Shesanyankena Charamena (Remainders by Last Digit)

        Computes the remainder using a pure digit-by-digit long division
        method (no use of % or //), matching Vedic remainder computation.

        Parameters:
            n (int): Dividend.
            d (int): Divisor.

        Returns:
            int: Remainder.
        """

        if d == 0:
            return {"type": "error", "message": "Divisor cannot be zero."}

        # find multiplier m such that (last digit)*m ≡ -1 (mod 10)
        last = d % 10
        m = None

        for k in range(1, 10):
            if (last * k) % 10 == 9:   # -1 mod 10 ≡ 9
                m = k
                break

        if m is None:
            return {"type": "error", "message": "No valid multiplier found."}

        steps = []
        current = n

        while abs(current) > 20:   # reduce until small
            ld = current % 10
            rest = current // 10
            new = rest + ld * m

            steps.append(f"{current} → {rest} + {ld}×{m} = {new}")
            current = new

        remainder = current % d

        return {
            "type": "ok",
            "n": n,
            "d": d,
            "multiplier": m,
            "steps": steps,
            "remainder": remainder,
            "divisible": remainder == 0,
            "message": f"Remainder = {remainder}, divisible = {remainder == 0}"
        }

    #13 Sopaantyadvayamantyam 
    @staticmethod
    def _sopantyadvayamantyam(n: int):
        """
        Sopantyadvayamantyam (Ultimate and Twice the Penultimate)

        Used for checking divisibility by 11.

        Algorithm:
            Alternately add and subtract digits from rightmost side.
            If final value is divisible by 11, so is original number.

        Example:
            121 → 1 - 2 + 1 = 0 → divisible by 11

        Parameter:
            n (int): Number to test.

        Returns:
            bool: True if divisible by 11, else False.
        """
        steps = []
        current = abs(n)

        while current >= 100:
            last = current % 10
            pair = (current // 10) % 100      # extract last two digits without last digit
            new = pair - last

            steps.append(f"{current} → ({pair}) - ({last}) = {new}")

            current = abs(new)

        remainder = current % 11

        return {
            "original": n,
            "steps": steps,
            "final_small": current,
            "remainder": remainder,
            "divisible": remainder == 0,
            "message": f"Remainder = {remainder} → Divisible? {remainder==0}"
        }

    #14 Ekanyunena Purvena 
    @staticmethod
    def _ekanyunena_purvena(N: int, n_digits: int):
        """
        Ekanyunena Purvena (By One Less Than the Previous)

        Used to multiply a number by 99, 999, 9999… (all 9’s).

        Concept:
            If multiplier is 9…9 (k times):
                Result = (N - 1) concatenated with (9...9 - (N - 1))

        Example:
            87 × 999:
                Left  = 86
                Right = 999 - 86 → 913
                Result = 86913

        Parameters:
            N (int): Number to multiply.
            digits (int): Count of 9's.

        Returns:
            int: Product.
        """
        # --- Error handling ---
        if N <= 0:
            return {"type": "error", "message": "N must be positive."}

        if n_digits <= 0:
            return {"type": "error", "message": "Number of 9s must be positive."}

        # --- Generate multiplier (999...9) ---
        multiplier = int("9" * n_digits)

        # --- Vedic Left part ---
        L = N - 1

        # --- Vedic Right part = 9's complement of Left ---
        R = multiplier - L

        # Always pad Right with n digits
        R_str = str(R).zfill(n_digits)

        # --- Final numeric result ---
        result = int(str(L) + R_str)

        # --- Return complete data ---
        return {
            "type": "ok",
            "N": N,
            "n_digits": n_digits,
            "multiplier": multiplier,
            "left": L,
            "right": R,
            "right_str": R_str,
            "result": result
        }

    #15 Gunitasamuchyah 
    @staticmethod
    def _gunitasamuccaya(a: float, b: float, c: float, d: float = None):
        """
        Gunitasamuccayah (Product of Sums)

        Two uses:

        1. Proportion Checking:
            a/b = c/d  →  a*d = b*c

        2. Missing Term:
            d = (b*c)/a

        Parameters:
            a,b,c (float)
            d (float, optional)

        Returns:
            float or bool:
                - True/False for proportion validity
                - Computed missing term if d not provided
        """

        # Case 1: Check proportion (a/b = c/d)
        if d is not None:
            lhs = a * d
            rhs = b * c

            return {
                "type": "check",
                "a": a, "b": b, "c": c, "d": d,
                "ad": lhs,
                "bc": rhs,
                "equal": abs(lhs - rhs) < 1e-9,
                "message": f"ad = {lhs}, bc = {rhs}, proportional = {abs(lhs-rhs)<1e-9}"
            }

        # Case 2: Solve for d in: a/b = c/d
        # d = bc / a
        if a == 0:
            return {"type": "error", "message": "a cannot be zero when solving for d."}

        d_val = (b * c) / a

        return {
            "type": "solve",
            "a": a, "b": b, "c": c,
            "d": d_val,
            "message": f"d = bc/a = {b}×{c}/{a} = {d_val}"
        }

    #16 Gunakasamuchyah 
    @staticmethod
    def _gunakasamuccaya(a: int, b: int, c: int):
        """
        Gunakasamuccayah (Factorization of Quadratics)

        Factorizes the quadratic:
            a*x^2 + b*x + c

        Uses Vedic p,q method:
            - Find integers p,q such that:
                p + q = b
                p * q = a*c
            - Then factor into:
                (a*x + p)(x + q/a)

        Implemented to return SymPy-style
            "(A*x + B)*(C*x + D)"

        Parameters:
            a,b,c (int): Quadratic coefficients.

        Returns:
            str: Factorized polynomial expression, or "irreducible".
        """

        ac = a * c
        p = q = None

        # Step 1: Find p and q satisfying p+q=b and p*q=ac
        for i in range(-abs(ac), abs(ac) + 1):
            if i == 0: continue
            if ac % i == 0:
                j = ac // i
                if i + j == b:
                    p, q = i, j
                    break

        if p is None:
            return {
                "type": "irreducible",
                "message": f"No p, q satisfying p+q={b}, pq={ac}",
                "factorization": None
            }

        # Step 2: rewrite middle term
        # ax^2 + px + qx + c
        # group as: (ax^2 + px) + (qx + c)

        # Factor each group
        import math
        g1 = math.gcd(a, p)
        g2 = math.gcd(q, c)

        # Group 1: ax^2 + px = g1 * ( (a/g1)x^2 + (p/g1)x )
        A1 = g1
        B1x = a // g1
        B1c = p // g1

        # Group 2: qx + c = g2 * ( (q/g2)x + (c/g2) )
        A2 = g2
        B2x = q // g2
        B2c = c // g2

        # Check the binomial match (must be equal)
        if B1x != B2x or B1c != B2c:
            # if mismatch, adjust signs
            if B1x == -B2x and B1c == -B2c:
                A2 = -A2
            else:
                return {
                    "type": "factorable",
                    "message": f"Factorization found but grouping mismatch; use standard factor pairs.",
                    "factorization": f"({A1}x + {A2})({B1x}x + {B1c})"
                }

        # Final factors:
        factor1 = f"{A1}x + {A2}"
        factor2 = f"{B1x}x + {B1c}"

        return {
            "type": "factorable",
            "a": a, "b": b, "c": c,
            "ac": ac,
            "p": p,
            "q": q,
            "factor1": factor1,
            "factor2": factor2,
            "message": f"({factor1})({factor2})"
        }

    # GIF Functions 

    @staticmethod
    def make_gif_ekadhikena(n: int, out_path="ekadhikena.gif", fps=0.25):

        """
Generate GIF for Ekadhikena Purvena Sutra (Square numbers ending in 5)

This GIF visually explains the step-by-step execution of the
Ekadhikena Purvena method by showing:
    • Splitting n into (a | 5)
    • Computing a × (a + 1)
    • Appending "25"
    • Final square result

Parameters:
    n (int):
        Number ending in 5 whose square is to be visualized.
    out_path (str):
        File path where the generated GIF will be saved.
    fps (int):
        Frames Per Second — controls animation speed.
        Higher fps = faster animation, lower fps = slower.

Returns:
    None
    (Saves GIF file to the specified output path.)
"""

        base, prod, result = VedicSutraGIF._ekadhikena_purvena(n)
        frames = [
            rf"$n = {n}$",
            rf"Split: {base} | 5",
            rf"{base} × ({base}+1) = {prod}",
            rf"Append 25 → {prod}25",
            rf"Result: {n}² = {result}"
        ]
    

        fig, ax = plt.subplots(figsize=(8, 3))
        ax.axis("off")
        txt = ax.text(0.5, 0.5, "", ha="center", va="center", fontsize=32, color="blue")
        def update(i):
            txt.set_text(frames[i])
            return (txt,)
        anim = FuncAnimation(fig, update, frames=len(frames), interval=1000, blit=True)
        anim.save(out_path, writer=PillowWriter(fps=fps))
        plt.close(fig)
        print(f"Saved Ekadhikena GIF to {out_path}")

    @staticmethod
    def make_gif_nikhilam(a: int, b: int, out_path="nikhilam.gif", fps=1):
        """
Generate GIF for Nikhilam Sutra (Multiplication near powers of 10)

This GIF demonstrates:
    • Selecting base (nearest power of 10)
    • Calculating deficiencies
    • Computing left part = a + deficiency(b)
    • Computing right part = da × db
    • Combining parts to get final product

Parameters:
    a (int): First number.
    b (int): Second number.
    out_path (str): Path for saving GIF.
    fps (int): Animation speed.

Returns:
    None
"""

        base, diff_a, diff_b, left, right, display_left, right_str, result = \
            VedicSutraGIF._nikhilam_navatascaramam_dashatah(a, b)
        frames = [
            rf"$a={a},\ b={b}$",
            rf"Base = {base}",
            rf"Diffs: {a}-{base}={diff_a}, {b}-{base}={diff_b}",
            rf"Left = {a}+({diff_b}) = {left}",
            rf"Right = {diff_a} × {diff_b} = {right}",
            rf"Combine: {display_left} | {right_str}",
            rf"Result: {a} × {b} = {result}"
        ]
        fig, ax = plt.subplots(figsize=(9, 3))
        ax.axis("off")
        txt = ax.text(0.5, 0.5, "", ha="center", va="center", fontsize=28, color="darkgreen")
        def update(i):
            txt.set_text(frames[i])
            return (txt,)
        anim = FuncAnimation(fig, update, frames=len(frames), interval=2000, blit=True)
        anim.save(out_path, writer=PillowWriter(fps=fps))
        plt.close(fig)
        print(f"Saved Nikhilam GIF to {out_path}")

    @staticmethod
    def make_gif_urdhva(a: int, b: int, out_path="urdhva.gif", fps=1):
        """
Generate GIF explaining Urdhva–Tiryagbhyam (Vertical & Crosswise Multiplication)

The animation covers:
    • Vertical digit multiplication
    • Crosswise multiplication
    • Partial sum accumulation
    • Carry propagation
    • Final product construction

Parameters:
    a (int): First integer.
    b (int): Second integer.
    out_path (str): Output GIF file path.
    fps (int): Playback speed.

Returns:
    None
"""        
        a_val, b_val, result_str, result = VedicSutraGIF._urdhva_tiryagbhyam(a, b)
        frames = [
            rf"Ūrdhva–Tiryagbhyām (Vertically and Crosswise)",
            rf"Numbers: {a_val} × {b_val}",
            rf"Multiply digits crosswise and vertically",
            rf"Add carries step-by-step",
            # rf"Final Product = {result_str}",
            rf"Result: {a_val} × {b_val} = {result}"
        ]
        fig, ax = plt.subplots(figsize=(9, 3))
        ax.axis("off")
        txt = ax.text(0.5, 0.5, "", ha="center", va="center", fontsize=28, color="purple")
        def update(i):
            txt.set_text(frames[i])
            return (txt,)
        anim = FuncAnimation(fig, update, frames=len(frames), interval=2000, blit=True)
        anim.save(out_path, writer=PillowWriter(fps=fps))
        plt.close(fig)
        print(f"Saved Urdhva GIF to {out_path}")

    @staticmethod
    def make_gif_paravartya(dividend: int, divisor: int, out_path="paravartya.gif", fps=1):
        """
Generate GIF illustrating Paravartya Yojayet (Vedic Division Method)

Displays:
    • Stepwise division using repeated subtraction
    • Bringing digits down one-by-one
    • Constructing quotient
    • Showing remainder

Parameters:
    dividend (int): Number to divide.
    divisor (int): Divider.
    out_path (str): GIF save path.
    fps (int): Frames per second.

Returns:
    None
"""

        dividend, divisor, quotient, remainder, steps = VedicSutraGIF._paravartya_yojayet(dividend, divisor)

        frames = [ "Parāvartya Yojayet — Transpose and Apply" ] + steps + [
            f"Result → Quotient = {quotient}, Remainder = {remainder}"
        ]

        if not frames:
            raise ValueError("No frames generated! Check your steps logic.")

        fig, ax = plt.subplots(figsize=(10, 4))
        ax.axis("off")
        txt = ax.text(0.5, 0.5, "", ha="center", va="center",
                    fontsize=18, color="brown", wrap=True, fontweight='bold')

        def update(i):
            txt.set_text(frames[i])
            return (txt,)

        anim = FuncAnimation(fig, update, frames=len(frames), interval=2500, blit=True)
        writer = PillowWriter(fps=fps)
        anim.save(out_path, writer=writer)
        plt.close(fig)
        print(f"Saved Parāvartya Yojayet GIF to {out_path}")
  
    @staticmethod        
    def make_gif_shunyam(expr1: tuple, expr2: tuple, out_path="shunyam.gif", fps=1):
        """
Generate GIF for Shunyam Samyasamuccaye (Equal-sum → zero rule)

Shows:
    • Checking equal-sum condition (a+d = b+c)
    • Automatically assigning x = 0 if equal
    • Otherwise solving via cross multiplication
    • Handling infinite/no solutions

Parameters:
    pair1 (tuple): (a, b)
    pair2 (tuple): (c, d)
    out_path (str): GIF output file path.
    fps (int): Frames per second.

Returns:
    None
"""

        result = VedicSutraGIF._shunyam_samyasamuccaye(expr1, expr2)
        a, b, c, d = result["a"], result["b"], result["c"], result["d"]

        # Prepare frames according to case type
        frames = [
            rf"Given: $\frac{{x + {a}}}{{x + {b}}} = \frac{{x + {c}}}{{x + {d}}}$",
            rf"Cross multiply → $(x + {a})(x + {d}) = (x + {b})(x + {c})$",
            rf"Expand → $x^2 + ({a + d})x + {a*d} = x^2 + ({b + c})x + {b*c}$",
            rf"After canceling $x^2$: $x(({a + d}) - ({b + c})) = {b*c - a*d}$",
        ]

        if result["type"] == "unique":
            frames += [
                rf"$x = \frac{{{result['numerator']}}}{{{result['denominator']}}}$",
                rf"⇒ $x = {round(result['x'], 3)}$",
                rf" {result['message']}"
            ]

        elif result["type"] == "infinite":
            frames += [
                rf"Sums equal: ({a}+{d})=({b}+{c}) → {result['sum_left']}={result['sum_right']}",
                rf"and ad=bc → {a*d}={b*c}",
                rf" {result['message']}"
            ]

        elif result["type"] == "none":
            frames += [
                rf"Sums equal: ({a}+{d})=({b}+{c}) → {result['sum_left']}={result['sum_right']}",
                rf"but ad≠bc → {a*d}≠{b*c}",
                rf" {result['message']}"
            ]

        # Create animation
        fig, ax = plt.subplots(figsize=(9, 3))
        ax.axis("off")
        txt = ax.text(0.5, 0.5, "", ha="center", va="center", fontsize=28, color="darkgreen")

        def update(i):
            txt.set_text(frames[i])
            return (txt,)

        anim = FuncAnimation(fig, update, frames=len(frames), interval=2000, blit=True)
        anim.save(out_path, writer=PillowWriter(fps=fps))
        plt.close(fig)
        print(f"Saved Shunyam Samyasamuccaye GIF to {out_path}")

    @staticmethod
    def make_gif_anurupye(eq1: tuple, eq2: tuple, out_path="anurupye.gif", fps=1):
        """
Generate GIF for Anurupye Shunyam Anyat (Proportion → Variable becomes zero)

The GIF shows:
    • Detecting proportional coefficients
    • When proportional → set variable to 0
    • Otherwise using determinant method
    • Final solution visualization

Parameters:
    eq1 (tuple): (a1, b1, c1)
    eq2 (tuple): (a2, b2, c2)
    out_path (str): Output path for GIF.
    fps (int): Animation speed.

Returns:
    None
"""


        result = VedicSutraGIF._anurupye_shunyam_anyat(eq1, eq2)
        a1, b1, c1 = eq1
        a2, b2, c2 = eq2

        frames = [
            rf"Given equations:",
            rf"${a1}x + {b1}y = {c1}$",
            rf"${a2}x + {b2}y = {c2}$",
            rf"Check ratios:"
        ]

        frames += [
            rf"$a_1/a_2 = {a1}/{a2}$",
            rf"$b_1/b_2 = {b1}/{b2}$",
            rf"$c_1/c_2 = {c1}/{c2}$"
        ]

        # -------- Case: y = 0 --------
        if result["type"] == "y_zero":
            frames += [
                rf"$a_1/a_2 = c_1/c_2$ → y = 0",
                rf"Then $x = {c1}/{a1} = {round(result['x'],3)}$",
                rf"Solution: $(x,y) = ({round(result['x'],3)}, 0)$"
            ]

        # -------- Case: x = 0 --------
        elif result["type"] == "x_zero":
            frames += [
                rf"$b_1/b_2 = c_1/c_2$ → x = 0",
                rf"Then $y = {c1}/{b1} = {round(result['y'],3)}$",
                rf"Solution: $(x,y) = (0, {round(result['y'],3)})$"
            ]

        # -------- General Case --------
        elif result["type"] == "unique":
            frames += [
                rf"No proportional ratios → Solve normally",
                rf"$x = {round(result['x'],3)}$",
                rf"$y = {round(result['y'],3)}$"
            ]

        # -------- No Solution --------
        else:
            frames += [
                rf"Error: {result['message']}"
            ]

        # ---------- RENDER GIF ----------
        fig, ax = plt.subplots(figsize=(9, 3))
        ax.axis("off")
        txt = ax.text(
            0.5, 0.5, "",
            fontsize=28, color="purple",
            ha="center", va="center"
        )

        def update(i):
            txt.set_text(frames[i])
            return (txt,)

        anim = FuncAnimation(
            fig, update,
            frames=len(frames),
            interval=2000,
            blit=True
        )

        anim.save(out_path, writer=PillowWriter(fps=fps))
        plt.close(fig)

        print(f"Saved Anurupye Shunyam Anyat GIF to {out_path}")

    @staticmethod
    def make_gif_sankalana(eq1: tuple, eq2: tuple, out_path="sankalana.gif", fps=1):
        """
Generate GIF for Sankalana–Vyavakalanabhyam (Add–Subtract elimination)

Shows:
    • Adding equations to eliminate one variable
    • Subtracting equations
    • Solving final values of x and y
    • Handling determinant-based special cases

Parameters:
    eq1 (tuple): First equation coefficients.
    eq2 (tuple): Second equation coefficients.
    out_path (str): GIF file path.
    fps (int): Frames per second.

Returns:
    None
"""


        result = VedicSutraGIF._sankalana_vyavakalanabhyam(eq1, eq2)
        a1, b1, c1 = eq1
        a2, b2, c2 = eq2

        # Basic frames
        frames = [
            rf"Given equations:",
            rf"${a1}x + {b1}y = {c1}$",
            rf"${a2}x + {b2}y = {c2}$",
            rf"Perform Sankalana (Addition):",
            rf"$({a1}+{a2})x + ({b1}+{b2})y = {c1+c2}$",
            rf"Perform Vyavakalana (Subtraction):",
            rf"$({a1}-{a2})x + ({b1}-{b2})y = {c1-c2}$"
        ]

        # If unique solution
        if result["type"] == "unique":
            route = result["route"]
            D = result["D"]
            E = result["E"]
            A = result["A"]
            B = result["B"]
            F = result["F"]
            C = result["C"]

            # -----------------------------------------
            # CASE 1: Subtraction eliminated y → E = 0
            # -----------------------------------------
            if route == "subtract_D":
                frames += [
                    rf"Vyavakalana gives: $Dx + 0y = F$",
                    rf"$D = {D},\ F = {F}$",
                    rf"$x = F/D = {F}/{D} = {round(result['x'],3)}$",
                    rf"Substitute x into original equation:",
                    rf"$y = {round(result['y'],3)}$",
                    rf"Final Answer:",
                    rf"$x = {round(result['x'],3)},\ y = {round(result['y'],3)}$"
                ]

            # -----------------------------------------
            # CASE 2: Subtraction eliminated x → D = 0
            # -----------------------------------------
            elif route == "subtract_E":
                frames += [
                    rf"Vyavakalana gives: $0x + Ey = F$",
                    rf"$E = {E},\ F = {F}$",
                    rf"$y = F/E = {F}/{E} = {round(result['y'],3)}$",
                    rf"Substitute y into original equation:",
                    rf"$x = {round(result['x'],3)}$",
                    rf"Final Answer:",
                    rf"$x = {round(result['x'],3)},\ y = {round(result['y'],3)}$"
                ]

            # -----------------------------------------
            # CASE 3: Addition eliminated y → B = 0
            # -----------------------------------------
            elif route == "add_A":
                frames += [
                    rf"Sankalana gives: $Ax + 0y = C$",
                    rf"$A = {A},\ C = {C}$",
                    rf"$x = C/A = {C}/{A} = {round(result['x'],3)}$",
                    rf"Then $y = {round(result['y'],3)}$",
                    rf"Final Answer:",
                    rf"$x = {round(result['x'],3)},\ y = {round(result['y'],3)}$"
                ]

            # -----------------------------------------
            # CASE 4: Addition eliminated x → A = 0
            # -----------------------------------------
            elif route == "add_B":
                frames += [
                    rf"Sankalana gives: $0x + By = C$",
                    rf"$B = {B},\ C = {C}$",
                    rf"$y = C/B = {C}/{B} = {round(result['y'],3)}$",
                    rf"Then $x = {round(result['x'],3)}$",
                    rf"Final Answer:",
                    rf"$x = {round(result['x'],3)},\ y = {round(result['y'],3)}$"
                ]

            # -----------------------------------------
            # CASE 5: General fallback (Cramer)
            # -----------------------------------------
            elif route == "cramer":
                frames += [
                    rf"Direct add/sub couldn't eliminate a variable.",
                    rf"Using elimination (Cramer's method):",
                    rf"$x = \\frac{{c_1 b_2 - b_1 c_2}}{{a_1 b_2 - b_1 a_2}}$",
                    rf"$x = {round(result['x'],3)}$",
                    rf"$y = {round(result['y'],3)}$",
                    rf"Final Answer:",
                    rf"$x = {round(result['x'],3)},\ y = {round(result['y'],3)}$"
                ]

        # Non-unique cases
        else:
            frames += [
                rf"Error: {result['message']}"
            ]

        # ------------------- RENDER GIF -------------------
        fig, ax = plt.subplots(figsize=(9, 3))
        ax.axis("off")
        txt = ax.text(
            0.5, 0.5, "",
            ha="center", va="center",
            fontsize=26, color="purple"
        )

        def update(i):
            txt.set_text(frames[i])
            return (txt,)

        anim = FuncAnimation(fig, update, frames=len(frames),
                            interval=2000, blit=True)
        anim.save(out_path, writer=PillowWriter(fps=fps))
        plt.close(fig)

        print(f"Saved Sankalana–Vyavakalanabhyam GIF to {out_path}")

    @staticmethod
    def make_gif_purana(expr: tuple, out_path="purana.gif", fps=1):
        """
Generate GIF for Puranapuranabhyam (Completion–Non-completion method)

Visualization:
    • Setting up (x+a)/b = c/(x+d)
    • Cross-multiplying to quadratic form
    • Computing discriminant
    • Showing real / repeated / complex roots stepwise

Parameters:
    params (tuple): (a, b, c, d)
    out_path (str): GIF save file path.
    fps (int): Frames per second.

Returns:
    None
"""


        result = VedicSutraGIF._puranapuranabhyam(expr)
        a, b, c, d = expr

        frames = [
            rf"Given equation:",
            rf"$\frac{{x + {a}}}{{{b}}} = \frac{{{c}}}{{x + {d}}}$",
            rf"Cross multiply:",
            rf"$(x+{a})(x+{d}) = {b}{c}$",
            rf"Expand LHS:",
            rf"$x^2 + ({a}+{d})x + {a*d}$",
            rf"Form quadratic:",
            rf"$x^2 + ({a+d})x + ({a*d}-{b*c}) = 0$",
            rf"Compute discriminant:",
            rf"$D = ({a+d})^2 - 4({a*d}-{b*c})$"
        ]

        # SOLUTION DISPLAY BASED ON TYPE
        if result["type"] == "two_real":
            frames += [
                rf"Discriminant = {result['discriminant']} > 0",
                rf"Two real roots:",
                rf"$x_1 = {round(result['x1'],3)}$",
                rf"$x_2 = {round(result['x2'],3)}$"
            ]

        elif result["type"] == "one_real":
            frames += [
                rf"Discriminant = 0",
                rf"One repeated root:",
                rf"$x = {round(result['x'],3)}$"
            ]

        elif result["type"] == "complex":
            frames += [
                rf"Discriminant = {result['discriminant']} < 0",
                rf"Complex roots:",
                rf"$x = {round(result['real'],3)} \pm {round(result['imag'],3)}i$"
            ]

        else:
            frames += [
                rf"Error: {result['message']}"
            ]

        # Render GIF
        fig, ax = plt.subplots(figsize=(9, 3))
        ax.axis("off")
        txt = ax.text(0.5, 0.5, "", fontsize=28, color="darkred",
                    ha="center", va="center")

        def update(i):
            txt.set_text(frames[i])
            return (txt,)

        anim = FuncAnimation(fig, update, frames=len(frames),
                            interval=2000, blit=True)

        anim.save(out_path, writer=PillowWriter(fps=fps))
        plt.close(fig)

        print(f"Saved Puranapuranabhyam GIF to {out_path}")

    @staticmethod
    def make_gif_chalana(a: float, b: float, c: float, out_path="chalanakalanabhyam.gif", fps=1):
        """
Generate GIF explaining Chalana–Kalanabhyam (Quadratic solving method)

Shows:
    • Building discriminant D = b² - 4ac
    • Classifying types of roots
    • Calculating exact values
    • Displaying each computational step

Parameters:
    a, b, c (float): Quadratic coefficients.
    out_path (str): Output location for GIF.
    fps (int): Animation playback speed.

Returns:
    None
"""


        result = VedicSutraGIF._chalana_kalanabhyam(a, b, c)

        frames = [
            rf"Given quadratic:",
            rf"${a}x^2 + {b}x + {c} = 0$",
            rf"Chalana (Difference):",
            rf"$D = b^2 - 4ac$",
            rf"$D = {b}^2 - 4({a})({c})$"
        ]

        # If quadratic, D exists
        if "D" in result:
            D = result["D"]
            frames.append(rf"$D = {D}$")

        # -------- CASE: Two Real Roots --------
        if result["type"] == "two_real":
            frames += [
                rf"$D = {D} > 0 \Rightarrow \text{{Two Real Roots}}$",
                rf"$x_1 = \frac{{-b + \sqrt{{D}}}}{{2a}}$",
                rf"$x_1 = {round(result['x1'],3)}$",
                rf"$x_2 = \frac{{-b - \sqrt{{D}}}}{{2a}}$",
                rf"$x_2 = {round(result['x2'],3)}$"
            ]

        # -------- CASE: One Real Root --------
        elif result["type"] == "one_real":
            frames += [
                rf"$D = 0 \Rightarrow \text{{One Repeated Root}}$",
                rf"$x = \frac{{-b}}{{2a}}$",
                rf"$x = {round(result['x'],3)}$"
            ]

        # -------- CASE: Complex Roots --------
        elif result["type"] == "complex":
            frames += [
                rf"$D = {D} < 0 \Rightarrow \text{{Complex Roots}}$",
                rf"$x = {round(result['real'],3)} \pm {round(result['imag'],3)}i$"
            ]

        # -------- CASE: Linear Equation --------
        elif result["type"] == "linear":
            frames = [
                rf"Given linear equation:",
                rf"${b}x + {c} = 0$",
                rf"$x = -\frac{{{c}}}{{{b}}} = {round(result['x'],3)}$"
            ]

        # -------- CASE: Invalid --------
        else:
            frames = [
                rf"Error: {result['message']}"
            ]

        # -------- CREATE GIF --------
        fig, ax = plt.subplots(figsize=(9, 3))
        ax.axis("off")

        txt = ax.text(
            0.5, 0.5, "",
            ha="center", va="center",
            fontsize=28, color="darkblue"
        )

        def update(i):
            txt.set_text(frames[i])
            return (txt,)

        anim = FuncAnimation(
            fig, update,
            frames=len(frames),
            interval=2000,
            blit=True
        )

        anim.save(out_path, writer=PillowWriter(fps=fps))
        plt.close(fig)

        print(f"Saved Chalana–Kalanabhyam GIF to {out_path}")

    @staticmethod
    def make_gif_yaavadunam(n: int, base: Optional[int] = None, out_path="yaavadunam.gif", fps=1):
        """
Generate GIF for Yaavadunam (Whatever the deficiency)

The GIF demonstrates:
    • Base detection (10/100/1000)
    • Computing deficiency d
    • Left part = n - d
    • Right part = d², properly padded
    • Or fallback to digit-wise multiplication

Parameters:
    n (int): Number to square.
    base (int|None): Optional base override.
    out_path (str): File path for GIF.
    fps (int): Speed of animation.

Returns:
    None
"""

        result = VedicSutraGIF._yaavadunam(n, base)
        n = result["n"]
        base = result["base"]
        e = result["e"]
        left = result["left"]
        right_num = result["right_num"]
        pad = result["pad"]
        right_str = result["right_str"]
        final = result["result"]

        frames = [
            rf"Given number: {n}",
            rf"Chosen base: {base} (nearest power of 10)",
        ]

        if e < 0:
            frames += [
                rf"Below base → deficiency d = {abs(e)}",
                rf"Left part = N - d = {n} - {abs(e)} = {left}",
                rf"Right part = d^2 = {abs(e)}^2 = {right_num}",
                rf"Pad right part to {pad} digits → {right_str}",
            ]
        else:
            frames += [
                rf"Above base → excess e = {e}",
                rf"Left part = N + e = {n} + {e} = {left}",
                rf"Right part = e^2 = {e}^2 = {right_num}",
                rf"Pad right part to {pad} digits → {right_str}",
            ]

        frames.append(rf"Final: {n}^2 = {left}{right_str} = {final}")

        # Render GIF
        fig, ax = plt.subplots(figsize=(9, 3))
        ax.axis("off")
        txt = ax.text(0.5, 0.5, "", fontsize=28, ha="center", va="center", color="darkgreen")

        def update(i):
            txt.set_text(frames[i])
            return (txt,)

        anim = FuncAnimation(fig, update, frames=len(frames), interval=2000, blit=True)
        anim.save(out_path, writer=PillowWriter(fps=fps))
        plt.close(fig)

        print(f"Saved Yavadunam GIF to {out_path}")

    @staticmethod
    def make_gif_vyashti(a: int, b: int, out_path="vyashtisamashti.gif", fps=1):
        """
Generate GIF for Vyashtisamashti (Part–Whole Multiplication)

Shows:
    • Combining numbers into groups (a+b) and (c+d)
    • Multiplying group values
    • Constructing the final answer

Parameters:
    a, b, c, d (int)
    out_path (str): GIF output file path.
    fps (int): Animation speed.

Returns:
    None
"""
        r = VedicSutraGIF._vyashtisamashti(a, b)

        A, a_low = r["A"], r["a"]
        B, b_low = r["B"], r["b"]
        AB, Ab = r["AB"], r["Ab"]
        aB, ab = r["aB"], r["ab"]
        final = r["result"]

        frames = [
            rf"Compute: {a} × {b}",
            rf"Split numbers:",
            rf"{a} = {A}×10 + {a_low}",
            rf"{b} = {B}×10 + {b_low}",
            rf"Apply (A + a)(B + b):",
            rf"{a}×{b} = AB + Ab + aB + ab",
            rf"AB = {A}×{B}×100 = {AB}",
            rf"Ab = {A}×{b_low}×10 = {Ab}",
            rf"aB = {a_low}×{B}×10 = {aB}",
            rf"ab = {a_low}×{b_low} = {ab}",
            rf"Sum = {AB} + {Ab} + {aB} + {ab}",
            rf"Final = {final}"
        ]

        # Render GIF
        fig, ax = plt.subplots(figsize=(9, 3))
        ax.axis("off")
        txt = ax.text(0.5, 0.5, "", fontsize=28,
                    ha="center", va="center", color="brown")

        def update(i):
            txt.set_text(frames[i])
            return (txt,)

        anim = FuncAnimation(
            fig, update,
            frames=len(frames),
            interval=2000,
            blit=True
        )
        anim.save(out_path, writer=PillowWriter(fps=fps))
        plt.close(fig)

        print(f"Saved Vyashtisamashti GIF to {out_path}")

    @staticmethod
    def make_gif_shesha(n: int, d: int, out_path="shesha.gif", fps=1):
        """
Generate GIF for Shesanyankena Charamena (Last-digit remainder rule)

Displays:
    • Step-by-step long division
    • Repeated subtraction
    • Showing quotient formation
    • Final remainder

Parameters:
    n (int): Dividend.
    d (int): Divisor.
    out_path (str): Output GIF name.
    fps (int): Animation speed.

Returns:
    None
"""

        res = VedicSutraGIF._shesanyankena_charamena(n, d)

        if res["type"] != "ok":
            print("Error:", res["message"])
            return

        m = res["multiplier"]
        frames = [
            rf"Check divisibility:",
            rf"{n} ÷ {d}",
            rf"Last digit of divisor = {d % 10}",
            rf"Multiplier m found such that:",
            rf"(last_digit × m) ≡ -1 mod 10",
            rf"m = {m}",
            rf"Apply repeated reduction:"
        ]

        for step in res["steps"]:
            frames.append(step)

        frames += [
            rf"Final small number = {res['remainder']}",
            rf"Remainder mod {d} = {res['remainder']}",
            rf"Divisible? → {res['divisible']}"
        ]

        # Render GIF
        fig, ax = plt.subplots(figsize=(9, 3))
        ax.axis("off")
        txt = ax.text(
            0.5, 0.5, "", fontsize=28,
            ha="center", va="center", color="darkred"
        )

        def update(i):
            txt.set_text(frames[i])
            return (txt,)

        anim = FuncAnimation(
            fig, update, frames=len(frames),
            interval=2000, blit=True
        )

        anim.save(out_path, writer=PillowWriter(fps=fps))
        plt.close(fig)

        print(f"Saved Sheshanyankena Charamena GIF to {out_path}")

    @staticmethod
    def make_gif_sopantya(n: int, out_path="sopantya.gif", fps=1):
        """
Generate GIF for Sopantyadvayamantyam (Divisibility by 11)

Shows:
    • Alternating subtract-add rule
    • Flow of digits from right to left
    • Final divisibility test result

Parameters:
    n (int): Number to test.
    out_path (str): GIF save path.
    fps (int): Animation playback rate.

Returns:
    None
"""


        r = VedicSutraGIF._sopantyadvayamantyam(n)

        frames = [
            rf"Check divisibility of {n} by 11",
            rf"Rule: Last digit vs preceding pair",
            rf"Repeatedly compute: (pair before last) - last digit",
            rf"Start:"
        ]

        for step in r["steps"]:
            frames.append(step)

        frames += [
            rf"Final small number = {r['final_small']}",
            rf"Remainder mod 11 = {r['remainder']}",
            rf"Divisible? → {r['divisible']}"
        ]

        fig, ax = plt.subplots(figsize=(9, 3))
        ax.axis("off")
        txt = ax.text(
            0.5, 0.5, "", fontsize=30,
            ha="center", va="center",
            color="darkblue"
        )

        def update(i):
            txt.set_text(frames[i])
            return (txt,)

        anim = FuncAnimation(
            fig, update,
            frames=len(frames),
            interval=2000,
            blit=True
        )
        anim.save(out_path, writer=PillowWriter(fps=fps))
        plt.close(fig)

        print(f"Saved Sopantyadvayamantyam GIF to {out_path}")

    @staticmethod
    def make_gif_ekanyunena(N: int, n_digits: int, out_path="ekanyunena.gif", fps=1):
        """
Generate GIF explaining Ekanyunena Purvena (Multiplying by all 9’s)

Shows:
    • Left part = N - 1
    • Right part = (999...9 - left)
    • Constructing final concatenated result

Parameters:
    N (int): Number to multiply.
    digits (int): Count of 9s.
    out_path (str): Output file path.
    fps (int): Frames per second.

Returns:
    None
"""

        r = VedicSutraGIF._ekanyunena_purvena(N, n_digits)

        # Handle error case safely
        if r.get("type") == "error":
            print("Error", r["message"])
            return

        # Extract data
        multiplier = r["multiplier"]
        L = r["left"]
        R = r["right"]
        R_str = r["right_str"]
        final = r["result"]

        # Build GIF frames
        frames = [
            rf"Compute: {multiplier} × {N}",
            r"Using Ekanyunena Purvena (one less than previous)",
            rf"Step 1: Left part = N - 1 = {N} - 1 = {L}",
            rf"Step 2: Right = 9…9 - Left",
            rf"{multiplier} - {L} = {R}",
            rf"Pad right to {n_digits} digits → {R_str}",
            rf"Final result:",
            rf"{multiplier} × {N} = {L}{R_str} = {final}"
        ]

        # Render GIF
        fig, ax = plt.subplots(figsize=(9, 3))
        ax.axis("off")
        txt = ax.text(0.5, 0.5, "", fontsize=28,
                    ha="center", va="center", color="darkgreen")

        def update(i):
            txt.set_text(frames[i])
            return (txt,)

        anim = FuncAnimation(
            fig, update,
            frames=len(frames),
            interval=2000,
            blit=True
        )

        anim.save(out_path, writer=PillowWriter(fps=fps))
        plt.close(fig)

        print(f"Saved Ekanyunena Purvena GIF to {out_path}")

    @staticmethod
    def make_gif_gunitasamuccaya(a: float, b: float, c: float, d: float = None,
                                out_path="gunitasamuccaya.gif", fps=1):
        """
        Generate GIF for Gunitasamuccayah (Product of sums)

        Visualizes:
            • Checking proportion: a/b = c/d
            • OR computing missing term: d = (b*c)/a
            • Shows multiplication steps clearly

        Parameters:
            a,b,c,d (float)
            out_path (str): Destination GIF file.
            fps (int): Playback speed.

        Returns:
            None
        """
            
        
        r = VedicSutraGIF._gunitasamuccaya(a, b, c, d)
        frames = []

        # -------------- CHECK MODE --------------
        if r["type"] == "check":
            frames = [
                rf"Check proportion:",
                rf"{a}:{b}  ?=  {c}:{r['d']}",
                rf"Apply Gunitasamuccaya:",
                rf"Product of extremes = ad = {a}×{r['d']} = {r['ad']}",
                rf"Product of means = bc = {b}×{c} = {r['bc']}",
            ]

            if r["equal"]:
                frames.append(rf"Since ad = bc → Ratios are proportional ✔")
            else:
                frames.append(rf"Since ad ≠ bc → Not proportional ✖")

        # -------------- SOLVE MODE --------------
        elif r["type"] == "solve":
            frames = [
                rf"Solve proportion:",
                rf"{a}:{b} = {c}:d",
                rf"Using Gunitasamuccaya:",
                rf"ad = bc",
                rf"{a}×d = {b}×{c}",
                rf"d = bc/a = {b}×{c}/{a}",
                rf"d = {r['d']}"
            ]

        # -------------- ERROR MODE --------------
        else:
            frames = [rf"Error: {r['message']}"]

        # ---------- Render GIF ----------
        fig, ax = plt.subplots(figsize=(9, 3))
        ax.axis("off")
        txt = ax.text(
            0.5, 0.5, "",
            ha="center", va="center",
            fontsize=28,
            color="darkpurple" if hasattr(ax, "color") else "purple"
        )

        def update(i):
            txt.set_text(frames[i])
            return (txt,)

        anim = FuncAnimation(fig, update, frames=len(frames),
                            interval=2000, blit=True)
        anim.save(out_path, writer=PillowWriter(fps=fps))
        plt.close(fig)

        print(f"Saved Gunitasamuccaya GIF to {out_path}")

    @staticmethod
    def make_gif_gunakasamuccaya(a: int, b: int, c: int, out_path="gunakasamuccaya.gif", fps=0.25):
        """
Generate GIF for Gunakasamuccayah (Quadratic factorization)

Shows:
    • Finding p, q such that p+q=b and p*q=a*c
    • Breaking b term
    • Factoring by grouping
    • Resulting expression in factored form

Parameters:
    a,b,c (int): Quadratic coefficients.
    out_path (str): Output GIF name.
    fps (int): Frames per second.

Returns:
    None
"""


        r = VedicSutraGIF._gunakasamuccaya(a, b, c)

        frames = [
            rf"Factor: {a}x² + {b}x + {c}",
            rf"Gunita = ac = {a}×{c} = {a*c}",
            rf"Samuccaya = b = {b}",
            rf"Find p, q such that p+q = {b} and pq = {a*c}",
        ]

        # ---------- IRREDUCIBLE ----------
        if r["type"] == "irreducible":
            frames += [
                rf"Error: No integers p, q satisfy conditions",
                rf"Quadratic is irreducible over integers."
            ]

        # ---------- FACTORABLE ----------
        else:
            p = r['p']
            q = r['q']
            f1 = r['factor1']
            f2 = r['factor2']

            frames += [
                rf"Found p = {p}, q = {q}",
                rf"Rewrite middle term:",
                rf"{a}x² + {b}x + {c}",
                rf" = {a}x² + {p}x + {q}x + {c}",
                rf"Group terms:",
                rf"( {a}x² + {p}x ) + ( {q}x + {c} )",
                rf"Factor each group:",
                rf"{r['factor1']}  and  {r['factor2']}",
                rf"Final factorization:",
                rf"({f1})({f2})"
            ]

        # ---------- Render GIF ----------
        fig, ax = plt.subplots(figsize=(9, 3))
        ax.axis("off")
        txt = ax.text(
            0.5, 0.5, "",
            fontsize=28,
            ha="center", va="center",
            color="darkgreen"
        )

        def update(i):
            txt.set_text(frames[i])
            return (txt,)

        anim = FuncAnimation(fig, update, frames=len(frames),
                            interval=1800, blit=True)
        anim.save(out_path, writer=PillowWriter(fps=fps))
        plt.close(fig)

        print(f"Saved Gunakasamuccaya GIF to {out_path}")
