import ast
import os
import sys
from pathlib import Path

def find_definition_range(source: str, symbol_path: str) -> tuple[int, int] | None:
 """
    Finds the start and end line numbers for a given symbol path (e.g., 'ClassName', 'ClassName.method', 'function_name').
    [C: scripts/py_struct_tools.py:py_remove_def, scripts/py_struct_tools.py:py_move_def]
    """
 try:
  tree = ast.parse(source)
 except SyntaxError:
  return _find_definition_range_regex(source, symbol_path)
 parts = symbol_path.split('.')
 current_nodes = tree.body
 target_node = None
 for i, part in enumerate(parts):
  found = False
  for node in current_nodes:
   if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)) and node.name == part:
    if i == len(parts) - 1:
     target_node = node
    else:
     if isinstance(node, ast.ClassDef):
      current_nodes = node.body
    found = True
    break
  if not found:
   return None
 if target_node:
  start = target_node.lineno
  if target_node.decorator_list:
   start = target_node.decorator_list[0].lineno
  return (start, target_node.end_lineno)
 return None

def _find_definition_range_regex(source: str, symbol_path: str) -> tuple[int, int] | None:
 import re
 parts = symbol_path.split('.')
 symbol_name = parts[-1]
 if '.' in symbol_path:
  class_name = parts[0]
  class_pattern = r'class\s+' + re.escape(class_name) + r'\s*:'
  class_match = re.search(class_pattern, source)
  if not class_match:
   return None
  class_body_start = class_match.end()
  method_pattern = r'def\s+' + re.escape(symbol_name) + r'\s*\('
  method_match = re.search(method_pattern, source[class_body_start:])
  if not method_match:
   return None
  method_line_num = source[:class_body_start + method_match.start()].count('\n') + 1
  remaining = source[class_body_start + method_match.start():]
  end_pattern = r'\n(?=    def\s|\nclass\s|\Z)'
  end_match = re.search(end_pattern, remaining)
  if end_match:
   end_line = source[:class_body_start + method_match.start() + end_match.start()].count('\n') + 1
   return (method_line_num, end_line)
  return None
 else:
  func_pattern = r'def\s+' + re.escape(symbol_name) + r'\s*\('
  func_match = re.search(func_pattern, source)
  if not func_match:
   return None
  start = source[:func_match.start()].count('\n') + 1
  remaining = source[func_match.start():]
  end_match = re.search(r'\n(?=def\s|\Z)', remaining)
  if end_match:
   end = remaining[:end_match.start()].count('\n') + start
   return (start, end)
  return None

def shift_indentation(content: str, target_depth: int) -> str:
 """
    Shifts and normalizes the indentation of a code block to 1-space units.
    Detects the base indentation and scales relative indentation to 1-space.
    [C: scripts/py_struct_tools.py:py_add_def]
    """
 lines = content.splitlines()
 if not lines:
  return ""
 
 # 1. Find min indent of non-empty lines
 min_indent = sys.maxsize
 for line in lines:
  if line.strip():
   indent = len(line) - len(line.lstrip())
   if indent < min_indent:
    min_indent = indent
 if min_indent == sys.maxsize:
  min_indent = 0

 # 2. Try to detect indentation unit (width)
 indent_unit = 0
 for line in lines:
  if line.strip():
   indent = len(line) - len(line.lstrip())
   rel_indent = indent - min_indent
   if rel_indent > 0:
    if indent_unit == 0:
     indent_unit = rel_indent
    else:
     import math
     indent_unit = math.gcd(indent_unit, rel_indent)
 
 if indent_unit == 0:
  indent_unit = 1

 shifted_lines = []
 for line in lines:
  if line.strip():
   indent = len(line) - len(line.lstrip())
   rel_level = (indent - min_indent) // indent_unit
   shifted_lines.append(" " * (target_depth + rel_level) + line.lstrip())
  else:
   shifted_lines.append("")
 
 return "\n".join(shifted_lines) + ("\n" if content.endswith("\n") else "")

def py_remove_def(filepath: str, symbol_path: str) -> str:
 """
    Removes a definition from a file. Returns error message or empty string on success.
    """
 if not os.path.exists(filepath):
  return f"File not found: {filepath}"
 with open(filepath, 'r', encoding='utf-8') as f:
  content = f.read()
 range_info = find_definition_range(content, symbol_path)
 if not range_info:
  return f"Symbol '{symbol_path}' not found in {filepath}"
 start, end = range_info
 lines = content.splitlines(True)
 del lines[start-1:end]
 with open(filepath, 'w', encoding='utf-8', newline='') as f:
  f.writelines(lines)
 return ""

def py_add_def(filepath: str, symbol_path: str, new_content: str, anchor_type: str, anchor_symbol: str = None) -> str:
 """
    Adds a definition to a file.
    anchor_type: 'before', 'after', 'top', 'bottom'
    anchor_symbol: if 'before' or 'after', the symbol to anchor to.
    symbol_path: context path (e.g. 'ClassName' or '' for module level)
    """
 if not os.path.exists(filepath):
  return f"File not found: {filepath}"
 with open(filepath, 'r', encoding='utf-8') as f:
  content = f.read()
 lines = content.splitlines(True)
 target_depth = 0
 insert_idx = -1
 if symbol_path:
  range_info = find_definition_range(content, symbol_path)
  if not range_info:
   return f"Context symbol '{symbol_path}' not found"
  c_start, c_end = range_info
  target_depth = 1 
  if anchor_type == 'top':
   insert_idx = c_start
   for i in range(c_start, c_end):
    if '"""' in lines[i] or "'''" in lines[i]:
     for j in range(i, c_end):
      if lines[j].count('"""') + lines[j].count("'''") == 2 or (j > i and ('"""' in lines[j] or "'''" in lines[j])):
       insert_idx = j + 1
       break
     break
  elif anchor_type == 'bottom':
   insert_idx = c_end
 else:
  if anchor_type == 'top':
   insert_idx = 0
  elif anchor_type == 'bottom':
   insert_idx = len(lines)
 if anchor_type in ('before', 'after') and anchor_symbol:
  full_anchor_path = f"{symbol_path}.{anchor_symbol}" if symbol_path else anchor_symbol
  range_info = find_definition_range(content, full_anchor_path)
  if not range_info:
   return f"Anchor symbol '{anchor_symbol}' not found"
  a_start, a_end = range_info
  insert_idx = a_start - 1 if anchor_type == 'before' else a_end
 if insert_idx == -1:
  return f"Invalid anchor_type '{anchor_type}'"
 formatted_content = shift_indentation(new_content, target_depth)
 if not formatted_content.endswith('\n'):
  formatted_content += '\n'
 # Ensure at least one blank line between definitions
 if insert_idx > 0 and lines[insert_idx-1].strip():
  formatted_content = '\n' + formatted_content
 if insert_idx < len(lines) and lines[insert_idx].strip():
  formatted_content = formatted_content + '\n'
 lines.insert(insert_idx, formatted_content)
 with open(filepath, 'w', encoding='utf-8', newline='') as f:
  f.writelines(lines)
 return ""

def py_move_def(src_file: str, dest_file: str, symbol_path: str, dest_symbol_path: str, anchor_type: str, anchor_symbol: str = None) -> str:
 """
    Moves a definition from one location to another.
    """
 if not os.path.exists(src_file):
  return f"Source file not found: {src_file}"
 with open(src_file, 'r', encoding='utf-8') as f:
  src_content = f.read()
 range_info = find_definition_range(src_content, symbol_path)
 if not range_info:
  return f"Symbol '{symbol_path}' not found in {src_file}"
 start, end = range_info
 src_lines = src_content.splitlines(True)
 moved_content = "".join(src_lines[start-1:end])
 err = py_add_def(dest_file, dest_symbol_path, moved_content, anchor_type, anchor_symbol)
 if err:
  return err
 return py_remove_def(src_file, symbol_path)

def py_region_wrap(filepath: str, start_line: int, end_line: int, region_name: str) -> str:
 """
    Wraps a line range in region tags.
    """
 if not os.path.exists(filepath):
  return f"File not found: {filepath}"
 with open(filepath, 'r', encoding='utf-8') as f:
  lines = f.readlines()
 if start_line < 1 or end_line > len(lines) or start_line > end_line:
  return f"Invalid line range: {start_line}-{end_line}"
 indent = ""
 first_line = lines[start_line-1]
 if first_line.strip():
  indent = first_line[:len(first_line) - len(first_line.lstrip())]
 # Insert bottom first to avoid shifting top
 lines.insert(end_line, f"{indent}#endregion: {region_name}\n")
 lines.insert(start_line-1, f"{indent}#region: {region_name}\n")
 with open(filepath, 'w', encoding='utf-8', newline='') as f:
  f.writelines(lines)
 return ""

if __name__ == "__main__":
 # Simple CLI for internal use
 pass
