import ast
import sys

class ImGuiScopeLinter:
 def __init__(self):
  self.pairs = {
   'begin': 'end',
   'begin_child': 'end_child',
   'begin_group': 'end_group',
   'begin_combo': 'end_combo',
   'begin_main_menu_bar': 'end_main_menu_bar',
   'begin_menu_bar': 'end_menu_bar',
   'begin_menu': 'end_menu',
   'begin_tooltip': 'end_tooltip',
   'begin_popup': 'end_popup',
   'begin_popup_modal': 'end_popup',
   'begin_popup_context_item': 'end_popup',
   'begin_popup_context_window': 'end_popup',
   'begin_popup_context_void': 'end_popup',
   'begin_table': 'end_table',
   'begin_tab_bar': 'end_tab_bar',
   'begin_tab_item': 'end_tab_item',
   'push_style_var': 'pop_style_var',
   'push_style_color': 'pop_style_color',
   'push_font': 'pop_font',
   'push_id': 'pop_id',
   'push_item_width': 'pop_item_width',
   'push_text_wrap_pos': 'pop_text_wrap_pos',
   'push_clip_rect': 'pop_clip_rect',
   'tree_node': 'tree_pop',
   'tree_push': 'tree_pop',
   'indent': 'unindent',
  }
  self.starts = set(self.pairs.keys())
  self.ends = set(self.pairs.values())

 def check_source(self, source_code: str) -> list[str]:
  try:
   tree = ast.parse(source_code)
  except SyntaxError as e:
   return [f"Syntax error: {e}"]
  visitor = ImGuiVisitor(self.pairs)
  visitor.visit(tree)
  return visitor.errors

class ImGuiVisitor(ast.NodeVisitor):
 def __init__(self, pairs):
  self.pairs = pairs
  self.stack = []
  self.errors = []
  self.starts = set(pairs.keys())
  self.ends = set(pairs.values())

 def visit_FunctionDef(self, node):
  saved_stack = self.stack
  self.stack = []
  self.generic_visit(node)
  while self.stack:
   start_type, lineno = self.stack.pop()
   self.errors.append(f"Function '{node.name}': Unclosed scope '{start_type}' started at line {lineno}")
  self.stack = saved_stack

 def visit_AsyncFunctionDef(self, node):
  self.visit_FunctionDef(node)

 def visit_Call(self, node):
  func_name = self._get_func_name(node.func)
  if func_name:
   parts = func_name.split('.')
   if len(parts) >= 2 and parts[0] in ('imgui', 'ed', 'imgui_node_editor'):
    method = parts[-1]
    if method in self.starts:
     self.stack.append((method, node.lineno))
    elif method in self.ends:
     if not self.stack:
      self.errors.append(f"Extra '{method}' at line {node.lineno} (no matching start)")
     else:
      start_type, start_lineno = self.stack[-1]
      expected_end = self.pairs.get(start_type)
      if expected_end == method:
       self.stack.pop()
      else:
       self.stack.pop()
       self.errors.append(f"Mismatched scope: '{method}' at line {node.lineno} does not match '{start_type}' at line {start_lineno}")
  self.generic_visit(node)

 def _get_func_name(self, node):
  if isinstance(node, ast.Name):
   return node.id
  elif isinstance(node, ast.Attribute):
   value = self._get_func_name(node.value)
   if value:
    return f"{value}.{node.attr}"
  return None

def main():
 if len(sys.argv) < 2:
  print("Usage: python check_imgui_scopes.py <file1> <file2> ...")
  return
 linter = ImGuiScopeLinter()
 for path in sys.argv[1:]:
  try:
   with open(path, "r", encoding="utf-8") as f:
    source = f.read()
   errors = linter.check_source(source)
   if errors:
    print(f"Errors in {path}:")
    for err in errors:
     print(f"  {err}")
   else:
    print(f"{path}: OK")
  except Exception as e:
   print(f"Error reading {path}: {e}")

if __name__ == "__main__":
 main()
