"""External Editor Launcher - Opens files in external text editors for diff viewing."""
from __future__ import annotations

import os
import subprocess
import tempfile

# TODO(Ed): Eliminate these?
from pathlib import Path
from typing  import Optional, List, Dict, Any

from src.models import ExternalEditorConfig, TextEditorConfig


class ExternalEditorLauncher:
 def __init__(self, config: ExternalEditorConfig):
  """
  [C: src/mcp_client.py:_DDGParser.__init__, src/mcp_client.py:_TextExtractor.__init__]
  """
  self.config = config

 def get_editor(self, editor_name: Optional[str] = None) -> Optional[TextEditorConfig]:
  """
  [C: tests/test_external_editor.py:TestExternalEditorLauncher.test_get_editor_by_name, tests/test_external_editor.py:TestExternalEditorLauncher.test_get_editor_returns_default, tests/test_external_editor.py:TestExternalEditorLauncher.test_get_editor_unknown_name]
  """
  if editor_name:
   return self.config.editors.get(editor_name)
  return self.config.get_default()

 def build_diff_command(self, editor: TextEditorConfig, original_path: str, modified_path: str) -> List[str]:
  """
  [C: tests/test_external_editor.py:TestExternalEditorLauncher.test_build_diff_command, tests/test_external_editor_gui.py:test_verify_command_format, tests/test_external_editor_gui.py:test_verify_vscode_command_format]
  """
  cmd = [editor.path] + editor.diff_args + [original_path, modified_path]
  return cmd

 def launch_diff(self, editor_name: Optional[str], original_path: str, modified_path: str) -> Optional[subprocess.Popen]:
  """
  [C: src/gui_2.py:App._open_patch_in_external_editor, tests/test_external_editor.py:TestExternalEditorLauncher.test_launch_diff_file_not_found, tests/test_external_editor.py:TestExternalEditorLauncher.test_launch_diff_missing_editor, tests/test_external_editor.py:TestExternalEditorLauncher.test_launch_diff_success]
  """
  editor = self.get_editor(editor_name)
  if not editor:
   return None
  cmd = self.build_diff_command(editor, original_path, modified_path)
  try:
   return subprocess.Popen(cmd)
  except FileNotFoundError:
   return None

 def launch_editor(self, editor_name: Optional[str], file_path: str) -> Optional[subprocess.Popen]:
  editor = self.get_editor(editor_name)
  if not editor:
   return None
  try:
   return subprocess.Popen([editor.path, file_path])
  except FileNotFoundError:
   return None


_cached_vscode_config: Optional[TextEditorConfig] = None


def _find_vscode_in_registry() -> Optional[str]:
 paths = []
 reg_keys = [
  r"HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
  r"HKCU\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\*",
  r"HKLM\SOFTWARE\WOW6432Node\Microsoft\Windows\CurrentVersion\Uninstall\*",
 ]
 for key in reg_keys:
  try:
   result = subprocess.run(
       ["powershell", "-Command", f"Get-ItemProperty -Path '{key}' -ErrorAction SilentlyContinue | Where-Object {{ $_.DisplayName -like '*Visual Studio Code*' }} | Select-Object -ExpandProperty InstallLocation"],
       capture_output=True, text=True, timeout=5
   )
   for line in result.stdout.strip().split('\n'):
    line = line.strip()
    if line and line != "":
     exe_path = line.strip() + "\\Code.exe"
     if os.path.exists(exe_path):
      paths.append(exe_path)
  except Exception:
   pass
 if paths:
  return paths[0]
 return None


def _find_vscode_common_paths() -> Optional[str]:
 candidates = [
  r"C:\apps\Microsoft VS Code\Code.exe",
  r"C:\Program Files\Microsoft VS Code\Code.exe",
  r"C:\Program Files (x86)\Microsoft VS Code\Code.exe",
  os.path.expanduser(r"~\AppData\Local\Programs\Microsoft VS Code\Code.exe"),
 ]
 for path in candidates:
  if os.path.exists(path):
   return path
 return None


def auto_detect_vscode() -> Optional[TextEditorConfig]:
 global _cached_vscode_config
 if _cached_vscode_config is not None:
  return _cached_vscode_config
 vscode_path = _find_vscode_in_registry() or _find_vscode_common_paths()
 if vscode_path:
  _cached_vscode_config = TextEditorConfig(
   name="vscode",
   path=vscode_path,
   diff_args=["--new-window", "--diff"]
  )
 return _cached_vscode_config


def get_default_launcher(config: Optional[Dict[str, Any]] = None) -> ExternalEditorLauncher:
 """
 Caller passes the live config dict (typically app.config from the
 AppController). Direct file I/O (the legacy public functions on src.models) is an
 architectural smell (bypasses the controller state owner) and is
 forbidden by scripts/audit_no_models_config_io.py.
 [C: src/gui_2.py:App._open_patch_in_external_editor, src/gui_2.py:App._render_external_editor_panel]
 """
 editors_config = config.get("tools", {}).get("text_editors", {}) if config else {}
 default_editor = config.get("tools", {}).get("default_editor", {}).get("default_editor") if config else None
 ext_config = ExternalEditorConfig.from_dict({
  "editors":        editors_config,
  "default_editor": default_editor,
 })
 launcher = ExternalEditorLauncher(ext_config)
 if not launcher.config.editors:
  detected = auto_detect_vscode()
  if detected:
   launcher.config.editors["vscode"] = detected
   launcher.config.default_editor = "vscode"
 else:
  vscode = launcher.config.editors.get("vscode")
  if vscode and "--new-window" not in vscode.diff_args:
   vscode.diff_args = ["--new-window", "--diff"]
 return launcher


def create_temp_modified_file(content: str) -> str:
 """
 [C: src/gui_2.py:App._open_patch_in_external_editor, tests/test_external_editor.py:TestHelperFunctions.test_create_temp_modified_file]
 """
 with tempfile.NamedTemporaryFile(mode="w", suffix="_modified", delete=False, encoding="utf-8") as f:
  f.write(content)
  return f.name
