#!/usr/bin/env python3
"""عدسة النقوش — SAM + كلاسيكي"""
import cv2
import numpy as np
import base64, json, pickle, os
from http.server import HTTPServer, BaseHTTPRequestHandler

HTML_PAGE = """<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
    <title>عدسة النقوش - تفاعلي</title>
    <style>
        body { font-family: 'Segoe UI', sans-serif; background: #1e1e24; color: #f5f5f5; margin: 0; padding: 20px; display: flex; flex-direction: column; align-items: center; }
        h1 { color: #ffcc00; }
        .card { background: #2b2b36; padding: 20px; border-radius: 10px; width: 90%; max-width: 800px; margin-bottom: 20px; display: flex; flex-direction: column; gap: 15px; }
        .row { display: flex; justify-content: center; gap: 15px; flex-wrap: wrap; align-items: center; }
        .slider-group { display: flex; justify-content: space-between; align-items: center; }
        .slider-group label { flex: 1; }
        .slider-group input { flex: 2; margin: 0 10px; }
        .slider-group span { min-width: 30px; text-align: center; color: #ffcc00; font-weight: bold; }
        button, input[type=file]::file-selector-button { background: #ffcc00; color: #1e1e24; border: none; padding: 10px 20px; border-radius: 5px; cursor: pointer; font-weight: bold; }
        button:hover { background: #e6b800; }
        .result { display: flex; flex-direction: column; align-items: center; gap: 10px; }
        .result img { max-width: 100%; border-radius: 8px; border: 2px solid #444; background: #fff; }
        .compare { display: flex; gap: 10px; flex-wrap: wrap; justify-content: center; }
        .compare figure { margin: 0; text-align: center; }
        .compare img { max-width: 400px; max-height: 400px; border-radius: 8px; border: 2px solid #444; }
        figcaption { color: #aaa; font-size: 0.85em; margin-top: 5px; }
        #loading { display: none; color: #ffcc00; }
        #waitToast { display: none; position: fixed; top: 50%; left: 50%; transform: translate(-50%,-50%); z-index: 99998; background: #2b2b36; color: #ffcc00; padding: 20px 30px; border-radius: 12px; border: 1px solid #ffcc00; font-weight: bold; font-size: 16px; box-shadow: 0 4px 20px rgba(0,0,0,0.5); text-align: center; }
        #actionButtons { display: none; }
        #classicSliders { display: none; }
        #magnifier { display: none; position: fixed; z-index: 99999; width: 45px; height: 45px; border-radius: 50%; border: 2px solid #ffcc00; box-shadow: 0 0 6px rgba(255,204,0,0.5); pointer-events: none; overflow: hidden; background: #fff; }
        #magnifier img { position: absolute; top: 0; left: 0; max-width: none; max-height: none; border: none; border-radius: 0; }
        .no-callout { -webkit-touch-callout: none !important; -webkit-user-select: none !important; user-select: none !important; }
    </style>
</head>
<body>
    <div id="magnifier"><img id="magImg" src=""></div>
    <div id="waitToast">⏳ يرجى الانتظار...</div>
    <div class="card">
        <div class="row"><input type="file" id="imageInput" accept="image/*"></div>
        <div id="classicSliders">
            <div class="slider-group"><label>🎚️ حجم العزل (blockSize):</label><input type="range" id="minSlider" min="3" max="51" value="21" step="2"><span id="minVal">21</span></div>
            <div class="slider-group"><label>🎚️ حساسية العزل (C):</label><input type="range" id="maxSlider" min="2" max="30" value="10"><span id="maxVal">10</span></div>
            <div class="slider-group"><label>✏️ سُمك القلم:</label><input type="range" id="thickSlider" min="1" max="5" value="1"><span id="thickVal">1</span></div>
        </div>
        <div class="row">
            <button onclick="showClassicSliders();processImage()">🎨 استخراج (كلاسيكي)</button>
            <button onclick="enableSAM()">🧠 ذكاء اصطناعي</button>
        </div>
        <div class="row" id="actionButtons">
            <button id="eraserBtn" onclick="toggleEraser()">🧹 مسح</button>
            <button onclick="undoLast()">↩️ تراجع</button>
            <button onclick="downloadResult()">💾 تحميل</button>
        </div>
    </div>
    <div id="loading">⚙️ جاري المعالجة...</div>
    <div id="result" class="card result" style="display:none;"><div class="compare" id="imagesContainer"></div></div>
    <script>
    let currentResult=null,originalImageData=null,samActive=false,magActive=false,stepCount=0,eraserActive=false,magTimer=null;
    let imgScale=1,imgX=0,imgY=0,lastDist=0,isPinching=false,lastTX=0,lastTY=0;
    document.querySelectorAll('input[type=range]').forEach(s=>s.addEventListener('input',function(){document.getElementById(this.id.replace('Slider','Val')).textContent=this.value}));
    function showClassicSliders(){document.getElementById('classicSliders').style.display='block'}
    function showOriginal(e){if(!originalImageData)return;if(e.cancelable)e.preventDefault();document.getElementById('resultImg').src='data:image/jpeg;base64,'+originalImageData}
    function hideOriginal(e){if(!currentResult)return;if(e.cancelable)e.preventDefault();document.getElementById('resultImg').src='data:image/png;base64,'+currentResult}
    function toggleEraser(){eraserActive=!eraserActive;document.getElementById('eraserBtn').style.background=eraserActive?'#ef4444':'#ffcc00';document.getElementById('eraserBtn').style.color=eraserActive?'#fff':'#1e1e24';document.getElementById('eraserBtn').textContent=eraserActive?'🧹 مسح (مفعل)':'🧹 مسح'}
    async function processImage(){
      const file=document.getElementById('imageInput').files[0];if(!file)return alert('اختر صورة أولاً');
      document.getElementById('loading').style.display='block';document.getElementById('result').style.display='none';
      const fd=new FormData();fd.append('image',file);fd.append('min_val',document.getElementById('minSlider').value);fd.append('max_val',document.getElementById('maxSlider').value);fd.append('thickness',document.getElementById('thickSlider').value);
      try{const r=await fetch('/process',{method:'POST',body:fd});const d=await r.json();if(d.error){alert(d.error);return}
      currentResult=d.result_img;originalImageData=d.original;
      document.getElementById('imagesContainer').innerHTML='<figure><img id="resultImg" src="data:image/png;base64,'+d.result_img+'" style="cursor:pointer;" ontouchstart="showOriginal(event)" ontouchend="hideOriginal(event)" ontouchcancel="hideOriginal(event)" onmousedown="showOriginal(event)" onmouseup="hideOriginal(event)" onmouseleave="hideOriginal(event)"></figure>';
      document.getElementById('result').style.display='flex';document.getElementById('actionButtons').style.display='flex'}catch(e){alert('خطأ: '+e.message)}
      document.getElementById('loading').style.display='none'}
    async function enableSAM(){
      document.getElementById('classicSliders').style.display='none';stepCount=0;
      const file=document.getElementById('imageInput').files[0];if(!file)return alert('اختر صورة أولاً');
      document.getElementById('loading').style.display='block';document.getElementById('loading').textContent='🧠 جاري تحميل نموذج الذكاء الاصطناعي...';document.getElementById('result').style.display='none';samActive=false;
      const fd=new FormData();fd.append('image',file);
      try{const r=await fetch('/sam_init',{method:'POST',body:fd});const d=await r.json();if(d.error){alert(d.error);return}
      samActive=true;originalImageData=d.original;currentResult='';
      document.getElementById('imagesContainer').innerHTML=
        '<figure style="overflow:hidden;border-radius:8px;border:2px solid #444;position:relative"><img id="samImg" class="no-callout" src="data:image/jpeg;base64,'+d.original+'" style="cursor:crosshair;width:100%;display:block;border-radius:6px;transform-origin:0 0;" oncontextmenu="return false;"></figure>'+
        '<div class="row" style="margin:5px 0"><button onclick="undoLast()">↩️ تراجع</button><button id="eraserBtn2" onclick="toggleEraser()">🧹 مسح</button><button onclick="downloadResult()">💾 تحميل</button></div>'+
        '<figure><img id="samResult" src="data:image/png;base64,'+d.white+'" style="max-width:100%;border-radius:8px;border:2px solid #444;background:#fff;"></figure>';
      document.getElementById('result').style.display='flex';document.getElementById('actionButtons').style.display='flex';
      imgScale=1;imgX=0;imgY=0;isPinching=false;
      const img=document.getElementById('samImg');img.style.transform='scale(1) translate(0,0)';
      img.addEventListener('touchstart',magStart,{passive:false});img.addEventListener('touchmove',magMove,{passive:false});img.addEventListener('touchend',magEnd,{passive:false});img.addEventListener('touchcancel',magCancel,{passive:false})
      }catch(e){alert('خطأ: '+e.message)}
      document.getElementById('loading').style.display='none'}
    const MAGNIFY=2.5;
    function magStart(e){
      if(e.touches.length>1){
        isPinching=true;lastDist=Math.hypot(e.touches[0].clientX-e.touches[1].clientX,e.touches[0].clientY-e.touches[1].clientY);
        if(magTimer){clearTimeout(magTimer);magTimer=null}return}
      lastTX=e.touches[0].clientX;lastTY=e.touches[0].clientY;
      if(magTimer)clearTimeout(magTimer);
      magTimer=setTimeout(function(){
        magActive=true;
        document.getElementById('magnifier').style.display='block';
        updateMag(lastTX,lastTY);
      },400)}
    function magMove(e){
      if(e.touches.length>1&&isPinching){
        const dist=Math.hypot(e.touches[0].clientX-e.touches[1].clientX,e.touches[0].clientY-e.touches[1].clientY);
        const scale=dist/lastDist;lastDist=dist;
        imgScale=Math.max(1,Math.min(8,imgScale*scale));
        document.getElementById('samImg').style.transform='scale('+imgScale+') translate('+imgX+'px,'+imgY+'px)';
        if(magTimer){clearTimeout(magTimer);magTimer=null}if(magActive){magActive=false;document.getElementById('magnifier').style.display='none'}
        return}
      if(e.touches.length>1)return;
      lastTX=e.touches[0].clientX;lastTY=e.touches[0].clientY;
      if(magActive)updateMag(lastTX,lastTY)}
    function magEnd(e){if(magTimer){clearTimeout(magTimer);magTimer=null;return}if(!magActive||!samActive)return;document.getElementById('magnifier').style.display='none';magActive=false;
      const t=e.changedTouches[0],img=document.getElementById('samImg'),rect=img.getBoundingClientRect();
      const x=((t.clientX-rect.left)/rect.width)*100,y=((t.clientY-rect.top)/rect.height)*100;if(x<0||x>100||y<0||y>100)return;
      if(eraserActive)submitSAMErase(x,y);else submitSAMClick(x,y)}
    function magCancel(e){if(magTimer)clearTimeout(magTimer);magActive=false;document.getElementById('magnifier').style.display='none'}
    function updateMag(mx,my){
      const img=document.getElementById('samImg'),rect=img.getBoundingClientRect(),mag=document.getElementById('magnifier'),magImg=document.getElementById('magImg');
      let magX=mx-22,magY=my-140;if(magX<5)magX=5;if(magX>window.innerWidth-50)magX=window.innerWidth-50;if(magY<5)magY=my+50;
      mag.style.left=magX+'px';mag.style.top=magY+'px';
      magImg.src=img.src;magImg.style.width=(rect.width*(imgScale*2.5))+'px';magImg.style.height=(rect.height*(imgScale*2.5))+'px';
      magImg.style.left=(-(mx-rect.left)*(imgScale*2.5)+22)+'px';magImg.style.top=(-(my-rect.top)*(imgScale*2.5)+22)+'px'}
    async function submitSAMClick(x,y){
      stepCount++;
      document.getElementById('waitToast').style.display='block';
      const fd=new FormData();fd.append('image',document.getElementById('imageInput').files[0]);fd.append('x',x.toFixed(2));fd.append('y',y.toFixed(2));
      try{const r=await fetch('/sam_click',{method:'POST',body:fd});const d=await r.json();if(d.error){alert(d.error);document.getElementById('waitToast').style.display='none';return}
      document.getElementById('samResult').src='data:image/png;base64,'+d.result_img;currentResult=d.result_img
      }catch(e){alert('خطأ: '+e.message)};document.getElementById('waitToast').style.display='none'}
    async function submitSAMErase(x,y){
      stepCount++;
      document.getElementById('waitToast').style.display='block';document.getElementById('waitToast').textContent='🧹 جاري المسح...';
      const fd=new FormData();fd.append('image',document.getElementById('imageInput').files[0]);fd.append('x',x.toFixed(2));fd.append('y',y.toFixed(2));
      try{const r=await fetch('/sam_erase',{method:'POST',body:fd});const d=await r.json();if(d.error){alert(d.error);document.getElementById('waitToast').style.display='none';return}
      document.getElementById('samResult').src='data:image/png;base64,'+d.result_img;currentResult=d.result_img
      }catch(e){alert('خطأ: '+e.message)};document.getElementById('waitToast').textContent='⏳ يرجى الانتظار...';document.getElementById('waitToast').style.display='none'}
    async function undoLast(){
      if(stepCount<=0)return;stepCount--;
      const fd=new FormData();fd.append('step',String(stepCount));
      try{const r=await fetch('/sam_undo',{method:'POST',body:fd});const d=await r.json();if(d.error){alert(d.error);return}
      document.getElementById('samResult').src='data:image/png;base64,'+d.result_img;currentResult=d.result_img
      }catch(e){alert('خطأ: '+e.message)}}
    function downloadResult(){if(!currentResult)return;const a=document.createElement('a');a.download='extracted_drawing.png';a.href='data:image/png;base64,'+currentResult;a.click()}
    </script>
</body>
</html>"""

def process_image_bytes(img_bytes, min_val, max_val, thickness):
    img_array = np.frombuffer(img_bytes, np.uint8)
    img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
    if img is None: return None, "خطأ في قراءة الصورة"
    h, w = img.shape[:2]
    if w > 1000:
        ratio = 1000 / w; img = cv2.resize(img, (1000, int(h * ratio)))
    _, orig_buf = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 85])
    orig_b64 = base64.b64encode(orig_buf).decode()
    gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
    clahe = cv2.createCLAHE(clipLimit=3.0, tileGridSize=(8, 8))
    enhanced = clahe.apply(gray)
    blurred = cv2.GaussianBlur(enhanced, (5, 5), 0)
    bs = min_val if min_val % 2 == 1 else min_val + 1
    thresh = cv2.adaptiveThreshold(blurred, 255, cv2.ADAPTIVE_THRESH_GAUSSIAN_C, cv2.THRESH_BINARY_INV, bs, max_val)
    kernel = np.ones((2, 2), np.uint8)
    cleaned = cv2.morphologyEx(thresh, cv2.MORPH_OPEN, kernel, iterations=1)
    white = cv2.bitwise_not(cleaned)
    _, result_buf = cv2.imencode('.png', white)
    result_b64 = base64.b64encode(result_buf).decode()
    return {'original': orig_b64, 'result_img': result_b64}, None

def process_image_bytes_sam_init(img_bytes):
    from segment_anything import sam_model_registry, SamPredictor
    img_array = np.frombuffer(img_bytes, np.uint8)
    img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
    if img is None: return None, "خطأ في قراءة الصورة"
    h, w = img.shape[:2]
    if w > 800:
        ratio = 800 / w; img = cv2.resize(img, (800, int(h * ratio)))
    _, orig_buf = cv2.imencode('.jpg', img, [cv2.IMWRITE_JPEG_QUALITY, 85])
    orig_b64 = base64.b64encode(orig_buf).decode()
    sam = sam_model_registry['vit_b'](checkpoint='/var/www/html/sam_vit_b_01ec64.pth')
    predictor = SamPredictor(sam)
    predictor.set_image(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    with open('/tmp/sam_predictor.pkl', 'wb') as f: pickle.dump(predictor, f)
    with open('/tmp/sam_dims.pkl', 'wb') as f: pickle.dump({'img_h': img.shape[0], 'img_w': img.shape[1]}, f)
    white = np.ones_like(img) * 255
    cv2.imwrite('/tmp/sam_white.png', white)
    _, white_buf = cv2.imencode('.png', white)
    white_b64 = base64.b64encode(white_buf).decode()
    # Reset history
    for f in os.listdir('/tmp'): 
        if f.startswith('sam_hist_'): os.remove(f'/tmp/{f}')
    cv2.imwrite('/tmp/sam_hist_0.png', white)
    with open('/tmp/sam_count.txt', 'w') as f: f.write('0')
    return {'original': orig_b64, 'white': white_b64}, None

def process_image_bytes_sam_click(img_bytes, click_x, click_y):
    from segment_anything import SamPredictor
    img_array = np.frombuffer(img_bytes, np.uint8)
    img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
    if img is None: return None, "خطأ في قراءة الصورة"
    h, w = img.shape[:2]
    if w > 800:
        ratio = 800 / w; img = cv2.resize(img, (800, int(h * ratio)))
    with open('/tmp/sam_dims.pkl', 'rb') as f: dims = pickle.load(f)
    px = int((click_x / 100.0) * dims['img_w'])
    py = int((click_y / 100.0) * dims['img_h'])
    with open('/tmp/sam_predictor.pkl', 'rb') as f: predictor = pickle.load(f)
    masks, scores, logits = predictor.predict(point_coords=np.array([[px, py]]), point_labels=np.array([1]), multimask_output=False)
    mask = masks[0]; mask_uint8 = (mask * 255).astype(np.uint8)
    contours, _ = cv2.findContours(mask_uint8, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    white = cv2.imread('/tmp/sam_white.png')
    if white is None: white = np.ones_like(img) * 255
    cv2.drawContours(white, contours, -1, (0, 0, 0), 2)
    cv2.imwrite('/tmp/sam_white.png', white)
    # Save history
    with open('/tmp/sam_count.txt', 'r') as f: cnt = int(f.read().strip())
    cnt += 1
    cv2.imwrite(f'/tmp/sam_hist_{cnt}.png', white)
    with open('/tmp/sam_count.txt', 'w') as f: f.write(str(cnt))
    _, result_buf = cv2.imencode('.png', white)
    result_b64 = base64.b64encode(result_buf).decode()
    return {'result_img': result_b64}, None

def process_image_bytes_sam_undo(step):
    """Load history at given step"""
    hist_path = f'/tmp/sam_hist_{step}.png'
    if not os.path.exists(hist_path): return None, "لا يوجد خطوات للتراجع"
    white = cv2.imread(hist_path)
    cv2.imwrite('/tmp/sam_white.png', white)
    with open('/tmp/sam_count.txt', 'w') as f: f.write(str(step))
    _, result_buf = cv2.imencode('.png', white)
    result_b64 = base64.b64encode(result_buf).decode()
    return {'result_img': result_b64}, None

def process_image_bytes_sam_erase(img_bytes, click_x, click_y):
    """Erase at click point: draw white over the mask area"""
    from segment_anything import SamPredictor
    img_array = np.frombuffer(img_bytes, np.uint8)
    img = cv2.imdecode(img_array, cv2.IMREAD_COLOR)
    if img is None: return None, "خطأ في قراءة الصورة"
    h, w = img.shape[:2]
    if w > 800:
        ratio = 800 / w; img = cv2.resize(img, (800, int(h * ratio)))
    with open('/tmp/sam_dims.pkl', 'rb') as f: dims = pickle.load(f)
    px = int((click_x / 100.0) * dims['img_w'])
    py = int((click_y / 100.0) * dims['img_h'])
    with open('/tmp/sam_predictor.pkl', 'rb') as f: predictor = pickle.load(f)
    masks, scores, logits = predictor.predict(point_coords=np.array([[px, py]]), point_labels=np.array([1]), multimask_output=False)
    mask = masks[0]; mask_uint8 = (mask * 255).astype(np.uint8)
    contours, _ = cv2.findContours(mask_uint8, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
    white = cv2.imread('/tmp/sam_white.png')
    if white is None: white = np.ones_like(img) * 255
    # Draw WHITE over the area to erase (opposite of click)
    cv2.drawContours(white, contours, -1, (255, 255, 255), -1)  # filled white
    cv2.imwrite('/tmp/sam_white.png', white)
    with open('/tmp/sam_count.txt', 'r') as f: cnt = int(f.read().strip())
    cnt += 1
    cv2.imwrite(f'/tmp/sam_hist_{cnt}.png', white)
    with open('/tmp/sam_count.txt', 'w') as f: f.write(str(cnt))
    _, result_buf = cv2.imencode('.png', white)
    result_b64 = base64.b64encode(result_buf).decode()
    return {'result_img': result_b64}, None

class Handler(BaseHTTPRequestHandler):
    def do_GET(self):
        self.send_response(200)
        self.send_header('Content-Type', 'text/html; charset=utf-8')
        self.end_headers()
        self.wfile.write(HTML_PAGE.encode())
    def do_POST(self):
        ct = self.headers['Content-Type']; cl = int(self.headers['Content-Length'])
        body = self.rfile.read(cl); boundary = ct.split('=')[1].encode()
        parts = body.split(b'--' + boundary)
        path = self.path
        if path == '/process':
            img_bytes = None; min_val = 21; max_val = 10; thickness = 1
            for p in parts:
                if b'Content-Disposition' not in p: continue
                if b'name="image"' in p: i = p.find(b'\r\n\r\n'); img_bytes = p[i+4:].rstrip(b'\r\n--') if i!=-1 else None
                elif b'name="min_val"' in p: i = p.find(b'\r\n\r\n'); min_val = int(p[i+4:].rstrip(b'\r\n--').decode()) if i!=-1 else min_val
                elif b'name="max_val"' in p: i = p.find(b'\r\n\r\n'); max_val = int(p[i+4:].rstrip(b'\r\n--').decode()) if i!=-1 else max_val
                elif b'name="thickness"' in p: i = p.find(b'\r\n\r\n'); thickness = int(p[i+4:].rstrip(b'\r\n--').decode()) if i!=-1 else thickness
            if not img_bytes: self._json({'error': 'لم يتم العثور على الصورة'}); return
            r, e = process_image_bytes(img_bytes, min_val, max_val, thickness)
            if e: self._json({'error': e}); return
            self._json(r)
        elif path == '/sam_init':
            img_bytes = None
            for p in parts:
                if b'Content-Disposition' in p and b'name="image"' in p:
                    i = p.find(b'\r\n\r\n')
                    if i != -1: img_bytes = p[i+4:].rstrip(b'\r\n--')
                    break
            if not img_bytes: self._json({'error': 'لم يتم العثور على الصورة'}); return
            r, e = process_image_bytes_sam_init(img_bytes)
            if e: self._json({'error': e}); return
            self._json(r)
        elif path == '/sam_click':
            img_bytes = None; cx = 50.0; cy = 50.0
            for p in parts:
                if b'Content-Disposition' not in p: continue
                if b'name="image"' in p: i = p.find(b'\r\n\r\n'); img_bytes = p[i+4:].rstrip(b'\r\n--') if i!=-1 else None
                elif b'name="x"' in p: i = p.find(b'\r\n\r\n'); cx = float(p[i+4:].rstrip(b'\r\n--').decode()) if i!=-1 else cx
                elif b'name="y"' in p: i = p.find(b'\r\n\r\n'); cy = float(p[i+4:].rstrip(b'\r\n--').decode()) if i!=-1 else cy
            if not img_bytes: self._json({'error': 'لم يتم العثور على الصورة'}); return
            r, e = process_image_bytes_sam_click(img_bytes, cx, cy)
            if e: self._json({'error': e}); return
            self._json(r)
        elif path == '/sam_undo':
            step = 0
            for p in parts:
                if b'Content-Disposition' in p and b'name="step"' in p:
                    i = p.find(b'\r\n\r\n')
                    if i != -1: step = int(p[i+4:].rstrip(b'\r\n--').decode())
                    break
            r, e = process_image_bytes_sam_undo(step)
            if e: self._json({'error': e}); return
            self._json(r)
        elif path == '/sam_erase':
            img_bytes = None; cx = 50.0; cy = 50.0
            for p in parts:
                if b'Content-Disposition' not in p: continue
                if b'name="image"' in p: i = p.find(b'\r\n\r\n'); img_bytes = p[i+4:].rstrip(b'\r\n--') if i!=-1 else None
                elif b'name="x"' in p: i = p.find(b'\r\n\r\n'); cx = float(p[i+4:].rstrip(b'\r\n--').decode()) if i!=-1 else cx
                elif b'name="y"' in p: i = p.find(b'\r\n\r\n'); cy = float(p[i+4:].rstrip(b'\r\n--').decode()) if i!=-1 else cy
            if not img_bytes: self._json({'error': 'لم يتم العثور على الصورة'}); return
            r, e = process_image_bytes_sam_erase(img_bytes, cx, cy)
            if e: self._json({'error': e}); return
            self._json(r)
    def _json(self, data):
        self.send_response(200)
        self.send_header('Content-Type', 'application/json')
        self.end_headers()
        self.wfile.write(json.dumps(data).encode())
    def log_message(self, *a): pass

if __name__ == '__main__':
    port = 8085
    print(f"✍️ عدسة النقوش على http://localhost:{port}")
    HTTPServer(('0.0.0.0', port), Handler).serve_forever()
