"""Template applet demonstrating Winston patterns and best practices.

This module serves as both a template for creating new applets and a
comprehensive example illustrating key Winston design patterns including:
- Applet structure inheriting from Applet and InputPromptDialog
- Hotkey handlers using @KeyHandler.hotkey() decorator
- FormDialog usage with input_values() method
- FormElement types (LabelElement, ListElement, ButtonElement)
- ButtonElement with on_press callbacks
- Element manipulation via element_from_name() and refill()
- Accelerator keys for keyboard shortcuts
- Speech integration using say_strings()
- Input processing via ingest_input()

Additionally demonstrates:
- Running mypy type checker with Control+M
- Mypy error browser with Control+S
- Parsing structured error file format
- Launching pycoder at specific file:line from button callback

Example:
    Create a new applet by copying this template::

        class MyApplet(Applet, InputPromptDialog):
            def __init__(self, *args, **kwargs):
                super().__init__(*args, **kwargs)
                print("My applet initialized")

            @KeyHandler.hotkey("Control+X")
            def command_my_action(self, modified_name):
                self.say_strings("Action triggered")

Use as Template:
    1. Copy this file to new applet name
    2. Rename TemplateDialog to YourAppletDialog
    3. Update docstring to describe your applet's purpose
    4. Add your custom hotkeys and handlers
    5. Implement your ingest_input() logic if using input line
    6. Add FormDialogs or other UI as needed following the examples
"""

import os
import re
from typing import Any
from ..key_handler import KeyHandler
from ..elements import Element
from ..form_elements import LabelElement, ListElement, ButtonElement
from ..subdialogs import Applet
from ..subdialogs import InputPromptDialog, InputPromptElement
from ..framework import beep
from ..format_mypy import format_mypy_output
from .editor import Source


def on_open_in_pycoder(button: Any) -> None:
    """Launch pycoder at the file and line number of selected error.

    Parses the selected error line to extract filename and line number,
    then launches pycoder applet with cursor positioned at that location.

    Format expected: "module.py, context, line 123: error message"

    Args:
        button: The ButtonElement instance that was pressed.
    """
    dialog = button.dialog
    error_list = dialog.element_from_name('errors')

    if not error_list.values:
        dialog.say_strings('No errors to open')
        return

    # Get selected error line
    selected_error = error_list.value

    # Parse: "module.py, context, line 123: error message"
    match = re.match(r'^([^,]+),\s*[^,]+,\s*line\s+(\d+):', selected_error)
    if not match:
        dialog.say_strings('Cannot parse error format')
        return

    filename, line_num = match.groups()
    #convert from str to int and from 1-indexed to 0-indexed
    line_num = int(line_num)-1

    # Construct full path - format_mypy.py preserves paths relative to winston/
    # E.g., "applets/diary.py" or "dialog.py"
    # Current working directory is winston/winston, so just join directly
    file_path = os.path.join(os.getcwd(), filename)

    if not os.path.exists(file_path):
        dialog.say_strings(f'File not found: {file_path}')
        return

    # Launch pycoder at specific file:line
    # pycoder accepts filler as Source(file path,lineno,colno) and will open file_path and then navigate to line  
    dialog.app.launch_applet('pycoder', filler=Source(file_path,line_num,0), revert_to=dialog.home_dialog)  # type: ignore[has-type]


class TemplateDialog(Applet, InputPromptDialog):  # type: ignore[misc]
    """Example applet demonstrating Winston patterns.

    This applet showcases fundamental Winston features:
    - InputPromptDialog with echo functionality
    - Hotkey registration with Control+N (toggles text transformations)
    - FormDialog with mypy error browser (Control+S)
    - Element manipulation via callbacks
    - Speech integration for accessibility
    - Launching pycoder at specific file:line locations

    The dialog accepts text input and echoes it to output, with optional
    transformations (prepending asterisks, capitalizing) controlled via
    keyboard shortcuts.

    Attributes:
        prepend: If True, prepend "***" to input text.
        capitalise: If True, convert input text to uppercase.

    Hotkeys:
        Control+M: Run mypy and save formatted results to scratch/
        Control+N: Toggle prepending asterisks to input
        Control+Shift+N: Toggle capitalizing input
        Control+S: Show mypy error browser dialog

    Example:
        >>> dialog = TemplateDialog()
        >>> dialog.run()
        # Type text and press Enter to echo
        # Press Control+N to toggle prepend mode
        # Press Control+S to browse mypy errors
    """

    def __init__(self, *args: Any, **kwargs: Any) -> None:
        """Initialize the template applet.

        Sets up text transformation flags and displays usage instructions
        to the output area using print statements (lazy population method).

        Args:
            *args: Positional arguments passed to parent classes.
            **kwargs: Keyword arguments passed to parent classes.

        Note:
            Using print statements at the end of __init__ is the lazy way
            to populate InputPromptDialog. For more sophisticated content,
            override debugging.InputPromptDialog.content_elements().
        """
        self.prepend: bool = False
        self.capitalise: bool = False
        super().__init__(*args, **kwargs)
        print('Example dialog that echos input to output.')
        print('Control+M runs mypy type checker and saves formatted results to scratch/')
        print('Control+N illustrates standard way to implement hotkeys and use of Shift with a hotkey')
        print('Control+S launches mypy error browser')
        print('  F5 jumps to error list (works from any element)')
        print('  F7 opens selected error in pycoder at correct line')
        print('Accelerator keys are specified on element constructors but registered on the dialog')
        print('The use of print statements at the end of TemplateDialog.__init__ illustrates lazy way to populate InputPromptDialog.')
        print('for more sophisticated subclasses of InputPromptDialog override InputPromptDialog.content_elements()')
        print('Other Dialog subclasses should override generate_content')

    @KeyHandler.hotkey("Control+M")
    def command_run_mypy(self, modified_name: str) -> None:
        """Run mypy type checker, reformat output, and save to scratch/.

        Executes mypy on the entire Winston codebase, reformats the output
        to blind-friendly single-line format, and saves results to
        scratch/mypy_errors_complete.txt for review with Control+S.

        Format: "module.py, context, line 123: error message"

        Args:
            modified_name: Modified key name including modifiers (e.g., "Control+M").

        Note:
            After running this command, use Control+S to browse the errors.
        """
        import subprocess
        from pathlib import Path

        self.say_strings("Running mypy type checker...")

        try:
            # Get winston directory (parent of applets/) and parent directory
            winston_dir = Path(__file__).parent.parent
            parent_dir = winston_dir.parent

            # Run mypy from parent directory on winston package
            # This allows relative imports to work correctly
            result = subprocess.run(
                ['python', '-m', 'mypy', 'winston', f'--config-file=winston{os.sep}mypy.ini'],
                cwd=parent_dir,
                capture_output=True,
                text=True,
                timeout=60
            )

            # Combine stdout and stderr
            output_lines = (result.stdout + result.stderr).splitlines()

            # Format output using format_mypy module
            formatted_lines, summary_info = format_mypy_output(output_lines)

            # Save to scratch/mypy_errors_complete.txt
            scratch_dir = winston_dir / 'scratch'
            scratch_dir.mkdir(exist_ok=True)
            output_file = scratch_dir / 'mypy_errors_complete.txt'

            with open(output_file, 'w', encoding='utf-8') as f:
                f.write('\n'.join(formatted_lines))

            # Speak the summary from mypy
            if summary_info:
                # Add instruction to browse errors if any were found
                if formatted_lines:
                    self.say_strings(f"{summary_info}. Press Control+S to browse.")
                else:
                    self.say_strings(summary_info)
            else:
                # Fallback if no summary (shouldn't happen with normal mypy output)
                error_count = len(formatted_lines)
                if error_count == 0:
                    self.say_strings("No mypy errors found.")
                else:
                    self.say_strings(f"Found {error_count} errors. Press Control+S to browse.")

        except subprocess.TimeoutExpired:
            self.say_strings("Mypy command timed out after 60 seconds")
        except FileNotFoundError:
            self.say_strings("Error: mypy not found. Install with: pip install mypy")
        except Exception as e:
            self.say_strings(f"Error running mypy: {e}")

    @KeyHandler.hotkey("Control+S")
    def command_show_mypy_errors(self, modified_name: str) -> None:
        """Show mypy error browser dialog.

        Loads mypy errors from scratch/mypy_errors_complete.txt and displays
        them in a ListElement. Demonstrates:
        - Reading structured data from files
        - Populating ListElement with file contents
        - ButtonElement with accelerator key (F7)
        - Launching pycoder at specific file:line positions
        - Result handling for submit vs cancel

        Format of error file: "module.py, context, line 123: error message"

        Args:
            modified_name: Modified key name including modifiers (e.g., "Control+S").
        """
        # Load mypy errors from file
        error_file = os.path.join(os.getcwd(), 'scratch', 'mypy_errors_complete.txt')

        if not os.path.exists(error_file):
            self.say_strings('Error file not found. Run mypy first.')
            return

        try:
            with open(error_file, 'r', encoding='utf-8') as f:
                error_lines = [line.strip() for line in f if line.strip()]
        except Exception as e:
            self.say_strings(f'Error reading file: {e}')
            return

        if not error_lines:
            self.say_strings('No errors in file')
            return

        # Build FormDialog with error list and open button
        fields = [
            #(LabelElement, {
                #'name': 'help',
                #'value': f'{len(error_lines)} mypy errors. Select error and press F7 to open in pycoder.'
            #}),
            (ListElement, {
                'name': 'errors',
                'values': error_lines,
                'accelerator': 'F5'
            }),
            (ButtonElement, {
                'name': 'Open in Pycoder',
                'on_press': on_open_in_pycoder,
                'accelerator': 'F7'
            }),
            (ButtonElement, {'name': 'cancel'})
        ]

        results = self.input_values(title=f'Mypy Errors ({len(error_lines)} total)', fields=fields,say_labels=False)

        if not results or not isinstance(results, dict) or results['__focus__']['name'].lower() == 'cancel':
            self.say_strings('cancelled')

    @KeyHandler.hotkey("Control+N")
    def command_Amend_input(self, modified_name: str) -> None:
        """Toggle input text transformations.

        Without Shift modifier: Toggles prepending "***" to input text.
        With Shift modifier: Toggles capitalizing input text to uppercase.

        This demonstrates how to check for modifiers in hotkey handlers.

        Args:
            modified_name: Modified key name. Check for 'Shift' to determine
                which transformation to toggle.

        Example:
            Control+N: Toggle prepend asterisks
            Control+Shift+N: Toggle capitalization
        """
        if 'Shift' not in modified_name:
            self.prepend = not self.prepend
            self.say_strings(f"prepend with asterisks = {self.prepend}")
        else:
            self.capitalise = not self.capitalise
            self.say_strings(f"input string capitalisation is {self.capitalise}")

    def ingest_input(self, text: str) -> None:
        """Process input line and display transformed output.

        Applies configured transformations (prepend, capitalize) to input
        text and prints result to output area. Called when user presses
        Enter on the input line.

        Args:
            text: Input text from the input line.

        Note:
            This is a simple example. Real applets might parse commands,
            execute actions, or perform more complex text processing.
        """
        output_text = '***' + text if self.prepend else text
        output_text = output_text.upper() if self.capitalise else output_text
        print(output_text)