"""
Auto-generated Optimas-DSPy pipeline for agent: {{ metadata.get('id', agent_name) }}

This pipeline builds an Optimas CompoundAISystem by wrapping DSPy Signatures
derived from your playbook tasks via the Optimas DSPy adapter.
"""

from __future__ import annotations

from typing import Dict, Any, List, ClassVar
import os
import threading

import dspy
from optimas.arch.system import CompoundAISystem
from optimas.adapt.dspy import create_component_from_dspy


# -----------------------------
# Signature generation from spec
# -----------------------------
{% set tasks = spec.get('tasks', []) %}
{% if not tasks %}
# Fallback single-task signature when no tasks are defined
class DefaultTask(dspy.Signature):
	"""Default task generated by SuperOptiX."""
	instructions: ClassVar[str] = "You are a helpful assistant."
	query: str = dspy.InputField()
	response: str = dspy.OutputField()
{% else %}
{% for task in tasks %}
class {{ task.get('name', 'Task' ~ loop.index)|to_pascal_case }}(dspy.Signature):
	"""{{ (task.get('description') or task.get('instruction') or 'Task generated by SuperOptiX')|clean }}"""
	instructions: ClassVar[str] = "{{ (task.get('instruction') or metadata.get('description') or 'You are a helpful assistant.')|clean }}"
{% for f in task.get('inputs', []) %}
	{{ f.get('name')|to_snake_case }}: {{ f.get('type','str') }} = dspy.InputField()
{% endfor %}
{% for f in task.get('outputs', []) %}
	{{ f.get('name')|to_snake_case }}: {{ f.get('type','str') }} = dspy.OutputField()
{% endfor %}

{% endfor %}
{% endif %}


class ThreadSafeDSPyLM:
	"""Thread-safe wrapper for DSPy LM to prevent threading issues."""
	
	def __init__(self, model_name: str, api_base: str, api_key: str):
		self.model_name = model_name
		self.api_base = api_base
		self.api_key = api_key
		self._lm = None
		self._lock = threading.Lock()
	
	def _get_lm(self):
		"""Get or create the DSPy LM instance."""
		if self._lm is None:
			with self._lock:
				if self._lm is None:
					try:
						self._lm = dspy.LM(
							model=self.model_name,
							api_base=self.api_base,
							api_key=self.api_key
						)
					except Exception as e:
						# Fallback to basic configuration if there are issues
						print(f"Warning: Could not configure DSPy LM with {self.model_name}: {e}")
						self._lm = dspy.LM(
							model="ollama/llama3.2:1b",
							api_base="http://localhost:11434",
							api_key="ollama"
						)
		return self._lm
	
	def __getattr__(self, name):
		"""Delegate attribute access to the underlying LM."""
		return getattr(self._get_lm(), name)


def build_optimas_system() -> CompoundAISystem:
	"""Construct a CompoundAISystem by wrapping DSPy signatures as components."""

	# Get configuration from environment or use defaults
	model_name = os.getenv("DSPY_MODEL", "ollama/llama3.2:1b")
	api_base = os.getenv("DSPY_API_BASE", "http://localhost:11434")
	api_key = os.getenv("DSPY_API_KEY", "ollama")
	
	# Configure DSPy with thread-safe LM wrapper
	# This prevents threading issues and provides fallback configuration
	thread_safe_lm = ThreadSafeDSPyLM(model_name, api_base, api_key)
	
	try:
		dspy.settings.configure(lm=thread_safe_lm._get_lm())
	except Exception as e:
		print(f"Warning: Could not configure DSPy settings: {e}")
		# Continue with default configuration

	components: Dict[str, Any] = {}

{% if not tasks %}
	# Default single-component system
	DefaultTaskModule = create_component_from_dspy(DefaultTask)
	components["default_task"] = DefaultTaskModule
	final_outputs: List[str] = ["response"]
{% else %}
	# Create one component per task in order
{% for task in tasks %}
	{{ task.get('name', 'Task' ~ loop.index)|to_snake_case }}_cls = {{ task.get('name', 'Task' ~ loop.index)|to_pascal_case }}
	{{ task.get('name', 'Task' ~ loop.index)|to_snake_case }}_module = create_component_from_dspy({{ task.get('name', 'Task' ~ loop.index)|to_pascal_case }})
	components["{{ task.get('name', 'Task' ~ loop.index)|to_snake_case }}"] = {{ task.get('name', 'Task' ~ loop.index)|to_snake_case }}_module
{% endfor %}
	# Use last task outputs as system final outputs
{% set last_task = tasks[-1] %}
	final_outputs: List[str] = [{% for f in last_task.get('outputs', []) %}"{{ f.get('name')|to_snake_case }}"{% if not loop.last %}, {% endif %}{% endfor %}]
{% endif %}

	# Robust evaluation function with error handling
	def eval_func(**kwargs) -> float:
		"""Evaluation function with error handling to prevent crashes."""
		try:
			# Returns 1.0 if any final output is non-empty, else 0.0
			for k in final_outputs:
				val = kwargs.get(k)
				if isinstance(val, str) and val.strip():
					return 1.0
				if val not in (None, "", [], {}):
					return 1.0
			return 0.0
		except Exception as e:
			print(f"Warning: Evaluation function error: {e}")
			return 0.0

	return CompoundAISystem(
		components=components,
		final_output_fields=final_outputs,
		ground_fields=[],
		eval_func=eval_func,
	)


# Convenience entrypoint expected by some tooling
def system_engine() -> CompoundAISystem:
	return build_optimas_system()


# Thread-safe cleanup function
def cleanup():
	"""Cleanup function to prevent threading issues."""
	try:
		if hasattr(dspy.settings, 'lm') and dspy.settings.lm is not None:
			# Clean up any resources if needed
			pass
	except Exception:
		pass

# Register cleanup on exit
import atexit
atexit.register(cleanup)


