default_max_attempts_per_step: 3

# Common processing script for all plotting steps
common_processing_script: &plotting_script |
  import matplotlib
  matplotlib.use("Agg")
  import matplotlib.pyplot
  import numpy
  import io
  import base64
  import re
  import sympy as sp
  
  # Get the user's function input from metadata
  user_function = metadata.get("user_function", "x")
  original_function = user_function

  try:
      # Support multiple functions separated by semicolon or comma
      function_list = re.split(r'[;,]', user_function)
      function_list = [f.strip() for f in function_list if f.strip()]
      
      # Colors for multiple functions
      colors = ['blue', 'red', 'green', 'orange', 'purple', 'brown', 'pink', 'gray']
      
      matplotlib.pyplot.figure(figsize=(10, 6))
      
      all_y_values = []
      function_info = []
      
      for i, func_str in enumerate(function_list):
          # Preprocess each function
          processed_func = func_str.replace('^', '**')
          processed_func = re.sub(r'(?<=\d)(?=[a-zA-Z])', '*', processed_func)
          processed_func = re.sub(r'(?<=[a-zA-Z])(?=\d)', '*', processed_func)
          
          # Enhanced function preprocessing
          enhanced_replacements = {
              'arctan': 'atan',
              'arcsin': 'asin', 
              'arccos': 'acos',
              'log': 'ln',
              'ln': 'log',  # Allow both ln and log
              'abs': 'Abs'
          }
          
          parsed_function = processed_func
          for old, new in enhanced_replacements.items():
              parsed_function = re.sub(r'\b' + old + r'\b', new, parsed_function)
          
          # Create sympy symbol and parse expression
          x_sym = sp.Symbol('x')
          expr = sp.sympify(parsed_function, locals={'x': x_sym})
          
          # Analyze function characteristics for dynamic range (inline)
          func_type = "other"
          if expr.has(sp.sin) or expr.has(sp.cos) or expr.has(sp.tan):
              func_type = "trigonometric"
          elif expr.has(sp.exp):
              func_type = "exponential"
          elif expr.has(sp.log):
              func_type = "logarithmic"
          elif expr.is_polynomial(x_sym):
              degree = sp.degree(expr, x_sym)
              if degree == 1:
                  func_type = "linear"
              elif degree == 2:
                  func_type = "quadratic"
              elif degree == 3:
                  func_type = "cubic"
          elif expr.has(sp.sqrt):
              func_type = "radical"
          elif expr.has(1/x_sym):
              func_type = "rational"
          
          # Determine optimal range inline
          if func_type == "trigonometric":
              x_range = (-2*numpy.pi, 2*numpy.pi)
          elif func_type == "exponential":
              x_range = (-3, 3)
          elif func_type == "logarithmic":
              x_range = (0.1, 10)
          elif func_type in ["linear", "quadratic", "cubic"]:
              x_range = (-10, 10)
          elif func_type == "rational":
              x_range = (-10, 10)
          else:
              x_range = (-5, 5)
          
          # Prepare x values with dynamic range
          x_vals = numpy.linspace(x_range[0], x_range[1], 400)
          
          # Convert to numpy function and evaluate
          func = sp.lambdify(x_sym, expr, 'numpy')
          y = func(x_vals)
          
          # Handle complex results
          if numpy.iscomplexobj(y):
              y = numpy.real(y)
          
          # Filter out infinite/NaN values for better plotting
          valid_mask = numpy.isfinite(y)
          x_vals_clean = x_vals[valid_mask]
          y_clean = y[valid_mask]
          
          if len(y_clean) > 0:
              all_y_values.extend(y_clean)
              color = colors[i % len(colors)]
              matplotlib.pyplot.plot(x_vals_clean, y_clean, 
                                   label=f'y = {func_str}', 
                                   color=color, linewidth=2)
          
          # Store function analysis info
          function_info.append({
              'function': func_str,
              'type': func_type,
              'range': x_range
          })
      
      # Dynamic y-axis limits based on all functions
      if all_y_values:
          y_min, y_max = numpy.percentile(all_y_values, [5, 95])
          y_range = y_max - y_min
          matplotlib.pyplot.ylim(y_min - 0.1*y_range, y_max + 0.1*y_range)
      
      # Enhanced plot styling
      matplotlib.pyplot.title(f'Plot of: {original_function}', fontsize=14, fontweight='bold')
      matplotlib.pyplot.xlabel('x', fontsize=12)
      matplotlib.pyplot.ylabel('y', fontsize=12)
      matplotlib.pyplot.grid(True, alpha=0.3)
      matplotlib.pyplot.legend(fontsize=10)
      
      # Generate function analysis inline
      analysis_parts = []
      for info in function_info:
          func_type = info['type']
          if func_type == "quadratic":
              analysis_parts.append(f"'{info['function']}' is a parabola (quadratic function)")
          elif func_type == "linear":
              analysis_parts.append(f"'{info['function']}' is a straight line (linear function)")
          elif func_type == "trigonometric":
              analysis_parts.append(f"'{info['function']}' shows periodic behavior (trigonometric)")
          elif func_type == "exponential":
              analysis_parts.append(f"'{info['function']}' shows exponential growth/decay")
          elif func_type == "logarithmic":
              analysis_parts.append(f"'{info['function']}' is a logarithmic curve")
          else:
              analysis_parts.append(f"'{info['function']}' is a {func_type} function")
      
      analysis_text = "; ".join(analysis_parts)
      
      buf = io.BytesIO()
      matplotlib.pyplot.tight_layout()
      matplotlib.pyplot.savefig(buf, format='png', dpi=100, bbox_inches='tight')
      matplotlib.pyplot.close()
      buf.seek(0)
      plot_image = base64.b64encode(buf.getvalue()).decode('utf-8')
      
      script_result = {
          "plot_image": plot_image,
          "function_analysis": analysis_text,
          "function_info": function_info
      }
      
  except Exception as e:
      # Handle errors gracefully with error message plot
      matplotlib.pyplot.figure()
      matplotlib.pyplot.text(0.5, 0.5, f'Error: Invalid function\n"{original_function}"\n\n{str(e)[:100]}...', 
                           horizontalalignment='center', verticalalignment='center',
                           transform=matplotlib.pyplot.gca().transAxes, fontsize=12,
                           bbox=dict(boxstyle="round,pad=0.3", facecolor="lightcoral"))
      matplotlib.pyplot.title('Function Error')
      matplotlib.pyplot.axis('off')
      buf = io.BytesIO()
      matplotlib.pyplot.savefig(buf, format='png')
      matplotlib.pyplot.close()
      buf.seek(0)
      plot_image = base64.b64encode(buf.getvalue()).decode('utf-8')
      
      script_result = {"plot_image": plot_image, "error": str(e)}

sections:
  - section_id: "section_1"
    title: "Math Plotter: Visualizing Functions"
    steps:
      - step_id: "step_1"
        title: "Introduction to Plotting"
        content_blocks:
          - "Welcome to the Math Plotter activity! 📈"
          - "In this activity, you'll learn how to plot mathematical functions and visualize them."
        question: "Are you ready to start plotting? Type 'yes' to begin."
        tokens_for_ai: |
          Determine if the user's response is 'yes' to proceed.
          If the user wants to change the language, categorize as 'set_language'.
        buckets:
          - proceed
          - set_language
        transitions:
          proceed:
            next_section_and_step: "section_1:step_2"
          set_language:
            content_blocks:
              - "Language preference updated. Please continue in your preferred language."
            metadata_add:
              language: "the-users-response"
            counts_as_attempt: false
            next_section_and_step: "section_1:step_1"

      - step_id: "step_2"
        title: "First Plot - Linear Function"
        content_blocks:
          - "Let's start by plotting a specific linear function! 📏"
          - "We'll plot: y = 2*x + 1"
        question: "Ready to plot y = 2*x + 1? Type 'yes' to see the graph."
        tokens_for_ai: |
          Check if the user entered a valid linear function. Accept any linear function like 'mx + b' format.
          Don't require analysis at this step - just check if it's a valid function.
          If the user wants to change the language, categorize as 'set_language'.
        processing_script: *plotting_script

        buckets:
          - proceed
          - set_language
        transitions:
          proceed:
            run_processing_script: True
            ai_feedback:
              tokens_for_ai: "Perfect! Here's the linear function y = 2*x + 1 plotted for you. Now you can explore plotting any functions you want!"
            metadata_add:
              user_function: "2*x + 1"
            next_section_and_step: "section_1:step_3"
          set_language:
            content_blocks:
              - "Language preference updated. Please continue in your preferred language."
            metadata_add:
              language: "the-users-response"
            counts_as_attempt: false
            next_section_and_step: "section_1:step_2"

      - step_id: "step_3"
        title: "Free Exploration - Plot Anything!"
        content_blocks:
          - "🎨 Time to explore! You can plot any function(s) you want."
          - "Try single functions: x**2, sin(x), exp(x), log(x), sqrt(x)"
          - "Try multiple functions: sin(x), cos(x) or x**2, 2*x + 1"
          - "Mix different types: sin(x), x**2, exp(-x)"
          - "Type 'done' when you're ready to finish."
        question: "Enter any function(s) to plot (or 'done' to complete):"
        tokens_for_ai: |
          This is a free exploration step. Accept any valid mathematical function(s).
          If user says 'done', 'finished', 'complete', etc., categorize as 'done'.
          If the user wants to change the language, categorize as 'set_language'.
          Otherwise, if it looks like a valid function, categorize as 'valid_function'.
        processing_script: *plotting_script

        buckets:
          - valid_function
          - done
          - invalid_function
          - set_language
        transitions:
          valid_function:
            run_processing_script: True
            ai_feedback:
              tokens_for_ai: "Great exploration! Here's your plot. Try another function or type 'done' to finish."
            metadata_add:
              user_function: "the-users-response"
              exploration_count: "n+1"
            counts_as_attempt: false
            next_section_and_step: "section_1:step_3"
          done:
            ai_feedback:
              tokens_for_ai: "Excellent exploration! You've completed the math plotting activity."
            metadata_add:
              score: "n+1"
            next_section_and_step: "section_2:step_1"
          invalid_function:
            ai_feedback:
              tokens_for_ai: "That doesn't look like a valid function. Try mathematical expressions like 'x**2' or 'sin(x)'."
            counts_as_attempt: false
            next_section_and_step: "section_1:step_3"
          set_language:
            content_blocks:
              - "Language preference updated. Please continue in your preferred language."
            metadata_add:
              language: "the-users-response"
            counts_as_attempt: false
            next_section_and_step: "section_1:step_3"

  - section_id: "section_2"
    title: "Plotting Complete"
    steps:
      - step_id: "step_1"
        title: "Completion"
        content_blocks:
          - "Congratulations! You've completed the math plotter activity."
          - "You've learned how to plot and visualize different types of functions."
