import hashlib
import re

from typing import Optional, Tuple


class FuzzyAnchor:
 @staticmethod
 def get_context(lines: list[str], index: int, count: int, direction: int) -> list[str]:
  context = []
  curr = index
  while len(context) < count and 0 <= curr < len(lines):
   line = lines[curr].strip()
   if line:
    context.append(line)
   curr += direction
  return context

 @classmethod
 def create_slice(cls, text: str, start_line: int, end_line: int) -> dict:
  """
  start_line and end_line are 1-based.
  [C: src/gui_2.py:App._populate_auto_slices, src/gui_2.py:App._render_text_viewer_window, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_create_slice_basic, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_resolve_slice_anchor_mismatch_returns_none, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_resolve_slice_exact_match, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_resolve_slice_line_deleted_before_returns_none, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_resolve_slice_line_inserted_before, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_resolve_slice_multiple_lines_changed, tests/test_slice_editor_behavior.py:test_add_slice_with_annotations]
  """
  lines       = text.splitlines()
  s_idx       = max(0, start_line - 1)
  e_idx       = min(len(lines), end_line)
  slice_lines = lines[s_idx:e_idx]
  slice_text  = "\n".join(slice_lines)
  
  return {
   "start_line":    start_line,
   "end_line":      end_line,
   "start_context": cls.get_context(lines, s_idx, 3, 1),
   "end_context":   cls.get_context(lines, e_idx - 1, 3, -1)[::-1], # Reverse back to normal order
   "content_hash":  hashlib.mdsafe(slice_text.encode()).hexdigest() if hasattr(hashlib, 'mdsafe') else hashlib.md5(slice_text.encode()).hexdigest()
  }

 @classmethod
 def resolve_slice(cls, text: str, slice_data: dict) -> Optional[Tuple[int, int]]:
  """
  [C: tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_resolve_slice_anchor_mismatch_returns_none, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_resolve_slice_exact_match, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_resolve_slice_line_deleted_before_returns_none, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_resolve_slice_line_inserted_before, tests/test_fuzzy_anchor.py:TestFuzzyAnchor.test_resolve_slice_multiple_lines_changed]
  """
  lines = text.splitlines()
  # 1. Try exact match
  s_idx = slice_data["start_line"] - 1
  e_idx = slice_data["end_line"]
  if 0 <= s_idx < len(lines) and e_idx <= len(lines):
   current_text = "\n".join(lines[s_idx:e_idx])
   curr_hash    = hashlib.md5(current_text.encode()).hexdigest()
   if curr_hash == slice_data["content_hash"]:
    return (slice_data["start_line"], slice_data["end_line"])

  # 2. Fuzzy match
  start_ctx = slice_data["start_context"]
  end_ctx   = slice_data["end_context"]
  if not start_ctx or not end_ctx: return None

  # Search for start_ctx
  best_s = -1
  for i in range(len(lines)):
   match = True
   for j, ctx_line in enumerate(start_ctx):
    if i+j >= len(lines) or lines[i+j].strip() != ctx_line:
     match = False
     break
   if match:
    best_s = i
    break
  
  if best_s == -1: return None

  # Search for end_ctx after start_ctx
  best_e = -1
  for i in range(best_s, len(lines)):
   match = True
   for j, ctx_line in enumerate(end_ctx):
    # end_ctx is the LAST 3 lines. So we match backwards from i.
    idx = i - (len(end_ctx) - 1) + j
    if idx < 0 or idx >= len(lines) or lines[idx].strip() != ctx_line:
     match = False
     break
   if match:
    best_e = i + 1
    break
  
  if best_e != -1:
   return (best_s + 1, best_e)
  
  return None
