"""
Base page template for Gradio applications.

This module provides a common base class for creating Gradio pages that load
and display apps from the apps directory. Each page should inherit from BasePage
and customize the behavior as needed.
"""

import gradio as gr
import importlib
from dataclasses import dataclass
from typing import List, Dict, Any, Optional, Callable
import yaml
from pathlib import Path
import warnings
import inspect
from abc import ABC, abstractmethod


@dataclass
class AppConfig:
    """Configuration for an individual app."""
    id: str
    title: str
    icon: str
    description: str
    enabled: bool
    config: dict = None


class BasePage(ABC):
    """Base class for Gradio pages that load and display apps."""
    
    def __init__(self, page_name: str, apps_dir: str = None):
        """
        Initialize the page.
        
        Args:
            page_name: Name of the page (mandatory).
            apps_dir: Directory name for apps. If None, uses page_name.
        """
        self.page_name = page_name
        self.apps_dir = apps_dir or page_name
        self._apps = None
    
    def _load_apps_from_yaml(self) -> Dict[str, AppConfig]:
        """Load app configurations from YAML file."""
        root_dir = Path(__file__).parent.parent
        path_obj = root_dir / self.apps_dir / "configs" / "apps.yaml"
        
        if not path_obj.exists():
            return {}
            
        with path_obj.open("r", encoding="utf-8") as f:
            data = yaml.safe_load(f)
            
        result = {}
        apps_dir = root_dir / self.apps_dir
        
        for k, app in data.get("apps", {}).items():
            app_file = apps_dir / f"{k}.py"
            if not app_file.is_file():
                error_msg = (
                    f"\n"
                    f"❌ App '{k}' is configured in apps.yaml but the corresponding Python file is missing!\n\n"
                    f"🔧 To fix this issue, choose one of the following options:\n"
                    f"   1. Create the missing file: '{self.apps_dir}/{k}.py'\n"
                    f"   2. Remove the '{k}' section from '{self.apps_dir}/configs/apps.yaml'\n\n"
                    f"📂 Expected file location: {app_file}\n"
                    f"📋 Config location: {path_obj}"
                    f"\n"
                )
                raise FileNotFoundError(error_msg)
                
            config_path = path_obj.parent / f"{k}.yaml"
            if app.get("enabled", True):
                config_data = None
                if config_path.is_file():
                    with config_path.open("r", encoding="utf-8") as cf:
                        config_data = yaml.safe_load(cf)
                result[k] = AppConfig(id=k, config=config_data, **app)
                
        return result
    
    def load_apps(self) -> Dict[str, AppConfig]:
        """Load all app configurations."""
        if self._apps is None:
            self._apps = self._load_apps_from_yaml()
        return self._apps
    
    def load_app_module(self, app_name: str):
        """Load a specific app module."""
        try:
            module = importlib.import_module(f"{self.apps_dir}.{app_name}")
            return module.get_interface
        except ImportError as e:
            raise ImportError(f"Failed to import app '{app_name}': {e}")
    
    @abstractmethod
    def get_page_title(self) -> str:
        """Get the title for this page."""
        pass
    
    @abstractmethod
    def get_page_theme(self) -> gr.Theme:
        """Get the theme for this page."""
        pass
    
    def create_home_tab(self, app_configs: Dict[str, AppConfig]) -> None:
        """Create the home tab with app listings."""
        with gr.TabItem("🏠 Home"):
            gr.Markdown("## Available Apps")
            gr.Markdown("Click on the tabs above to access different apps.")

            if app_configs:
                # List all available apps with config info
                apps_list = "\n".join([
                    f"- {app_config.icon} **{app_id}**: {app_config.description}"
                    for app_id, app_config in app_configs.items()
                ])
                gr.Markdown(f"### Apps in this launcher:\n{apps_list}")
            else:
                gr.Markdown("### No apps found in the apps directory.")
    
    def create_app_tab(self, app_id: str, app_config: AppConfig) -> None:
        """Create a tab for a specific app."""
        tab_label = f"{app_config.icon} {app_id}" if getattr(app_config, 'icon', None) else f"📱 {app_id}"
        
        with gr.TabItem(tab_label):
            try:
                # Create a container for the app UI
                with gr.Column() as app_container:
                    # Load and add the app interface to this container
                    self.load_app_module(app_id)(container=app_container)
                gr.Markdown(f"✅ App loaded successfully!")
            except Exception as e:
                gr.Markdown(f"## ❌ Error Loading {app_id}")
                gr.Markdown(f"**Error:** {str(e)}")
    
    def create_additional_content(self) -> None:
        """Override this method to add additional content before the tabs."""
        pass
    
    def filter_apps(self, app_configs: Dict[str, AppConfig]) -> Dict[str, AppConfig]:
        """Override this method to filter which apps to display."""
        return app_configs
    
    def create_page(self) -> gr.Blocks:
        """Create the complete Gradio page."""
        with gr.Blocks(title=self.get_page_title(), theme=self.get_page_theme()) as app:
            gr.Markdown(f"# 🚀 {self.get_page_title()}")
            
            # Allow subclasses to add additional content
            self.create_additional_content()
            
            app_configs = self.filter_apps(self.load_apps())

            if app_configs:
                with gr.Tabs():
                    # Create home tab
                    self.create_home_tab(app_configs)
                    
                    # Create a tab for each app
                    for app_id, app_config in app_configs.items():
                        self.create_app_tab(app_id, app_config)
            else:
                gr.Markdown("## No apps found in the apps directory.")
        
        return app

# Helper functions for apps (maintain backward compatibility)
def load() -> AppConfig:
    """Load config for the calling app (for backward compatibility)."""
    caller_frame = inspect.stack()[1]
    caller_path = Path(caller_frame.filename)
    
    # Get the apps directory name from the caller's parent directory
    apps_dir_name = caller_path.parent.name
    app_id = caller_path.stem
    
    # Use a temporary BasePage instance to load apps from the detected directory
    temp_page = type('TempPage', (BasePage,), {
        'get_page_title': lambda self: 'Temp',
        'get_page_theme': lambda self: gr.themes.Ocean()
    })(page_name='temp', apps_dir=apps_dir_name)
    
    apps = temp_page.load_apps()
    app = apps.get(app_id)
    if app is not None:
        return app
    warnings.warn(f"Config for app id '{app_id}' is missing in apps.yaml.")
    return None


def cache_dir() -> Path:
    """Get cache directory for the calling app (for backward compatibility)."""
    caller_frame = inspect.stack()[1]
    caller_path = Path(caller_frame.filename)
    
    # Get the apps directory name and app id
    apps_dir_name = caller_path.parent.name
    app_id = caller_path.stem
    
    # Create cache directory path including the apps directory name
    ret = Path(__file__).parent.parent / ".cache" / apps_dir_name / app_id
    ret.mkdir(parents=True, exist_ok=True)
    return ret
