#!/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,maximum-scale=1,user-scalable=no">
<title>عدسة النقوش</title>
<style>
*{box-sizing:border-box;margin:0;padding:0}
body{background:#1e1e24;color:#f5f5f5;font-family:system-ui;padding:15px;display:flex;flex-direction:column;align-items:center}
.card{background:#2b2b36;padding:12px;border-radius:10px;width:100%;max-width:800px}
.row{display:flex;flex-direction:row;gap:6px;flex-wrap:wrap;justify-content:center;margin-top:8px;align-items:center}
.btn{width:40px;height:40px;cursor:pointer;border-radius:6px;background:rgba(200,170,130,0.05);padding:4px}
#result{display:none;width:100%;max-width:800px;margin-top:10px;position:relative}
#result img{width:100%;border-radius:8px;border:2px solid #444;display:block;touch-action:none}
#loading{display:none;text-align:center;padding:10px}
#magnifier{display:none;position:fixed;z-index:99999;width:150px;height:150px;border-radius:50%;border:2px solid #ffcc00;pointer-events:none;overflow:hidden;background:#fff}
#magnifier img{position:absolute;top:0;left:0;max-width:none;max-height:none;border:none}
#magDot{position:absolute;top:50%;left:50%;width:5px;height:5px;background:red;border-radius:50%;transform:translate(-50%,-50%);z-index:2}
.no-callout{-webkit-touch-callout:none!important;-webkit-user-select:none!important;user-select:none!important}
#mainImg{-webkit-touch-callout:none!important;-webkit-user-select:none!important;user-select:none!important}
</style>
</head>
<body>
<img src="/naqsh-header.png" style="width:100%;max-width:600px;margin-bottom:8px;border-radius:8px">
<div id="magnifier"><img id="magImg"><div id="magDot"></div></div>
<div class="card"><div class="row">
<div style="position:relative;width:120px;height:40px;cursor:pointer;border-radius:8px;overflow:hidden;background:url('/start-btn-v3.jpg') center/cover no-repeat">
<input type="file" id="imageInput" accept="image/*" style="position:absolute;left:0;top:0;width:100%;height:100%;opacity:0;cursor:pointer;z-index:10">
</div>
</div></div>
<div id="loading" style="display:none;text-align:center;padding:10px"><img src="/loading-anim.gif" style="width:100px;height:auto;border-radius:6px"></div>
<div id="result">
<div style="position:relative" id="imgWrap" oncontextmenu="return false">
<img id="mainImg" draggable="false" oncontextmenu="return false" class="no-callout" style="width:100%;border-radius:8px;border:2px solid #444;display:block;touch-action:none">
<canvas id="overlayCanvas" style="display:none;position:absolute;top:0;left:0;width:100%;height:100%;pointer-events:none;border-radius:8px"></canvas>
</div>
<div class="row" style="width:100%;max-width:800px">
<img src="/undo-icon.png" onclick="undoLast()" title="تراجع" class="btn">
<img id="toggleBtn" src="/view-icon.png" onclick="toggleView()" title="الأصل" class="btn" style="opacity:0.4">
<img id="brushBtn" src="/brush-icon.png" onclick="toggleBrush()" title="فرشاة" class="btn">
<img src="/download-icon.png" onclick="downloadResult()" title="تحميل" class="btn">
</div>
</div>
<div id="waitToast" style="display:none;position:fixed;top:50%;left:50%;transform:translate(-50%,-50%);z-index:99998;background:#1e1e24;border:3px solid #ffcc00;padding:15px;border-radius:16px">⏳</div>
<script>
let stepCount=0,magActive=false,magTimer=null,isScrolling=false;
let samActive=false,brushMode=false,currentResult=null;
let layerState=0,layerCanvas=null,_curLayer=0;

function enableToggleBtn(){const b=document.getElementById('toggleBtn');b.style.opacity='1';b.style.cursor='pointer'}
function toggleBrush(){brushMode=!brushMode;const b=document.getElementById('brushBtn');b.style.background=brushMode?'#e6b800':''}

document.getElementById('imageInput').addEventListener('change',function(){if(this.files[0])startAnalysis()});

async function startAnalysis(){
 const f=document.getElementById('imageInput').files[0];if(!f)return;
 document.getElementById('loading').style.display='block';
 document.getElementById('result').style.display='none';
 const fd=new FormData();fd.append('image',f);
 try{
  const r=await fetch('/sam_init',{method:'POST',body:fd});const d=await r.json();
  if(d.error){alert(d.error);document.getElementById('loading').style.display='none';return}
  document.getElementById('mainImg').src='data:image/jpeg;base64,'+d.original;
  document.getElementById('result').style.display='block';samActive=true;
  document.getElementById('loading').style.display='none';
  enableClicks();
 }catch(e){alert('خطأ: '+e.message);document.getElementById('loading').style.display='none'}
}

function enableClicks(){
 const img=document.getElementById('mainImg');
 img.addEventListener('touchstart',magStart,{passive:false});
 img.addEventListener('touchmove',magMove,{passive:false});
 img.addEventListener('touchend',magEnd,{passive:false});
 img.addEventListener('touchcancel',magCancel,{passive:false});
}

async function submitClick(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}
  const img=document.getElementById('mainImg');
  if(!img._orig)img._orig=img.src;
  currentResult=d.result_img||d.overlay;
  goLayer(2);
 }catch(e){alert('خطأ: '+e.message)}
 document.getElementById('waitToast').style.display='none'}

var _curLayer=0;

function goLayer(n){
 const img=document.getElementById('mainImg');
 if(n==1){
  if(img._orig)img.src=img._orig;
  _curLayer=1;
 }else if(n==2&&currentResult&&img._orig){
  _curLayer=2;
  showOrigPlusSelection(img);
 }else if(n==3&&currentResult){
  _curLayer=3;
  img.src='data:image/png;base64,'+currentResult;
 }
 updateLayerBtn();
}

function updateLayerBtn(){
 const b=document.getElementById('toggleBtn');
 if(_curLayer==1){b.src='/view-icon.png';b.title='الأصل';b.style.opacity='1'}
 else if(_curLayer==2){b.src='/layer1-icon.png';b.title='الطبقة 1'}
 else if(_curLayer==3){b.src='/layer2-icon.png';b.title='الطبقة 2'}
}

gleView(){
 if(!currentResult)return;
 if(_curLayer==1)goLayer(2);
 else if(_curLayer==2)goLayer(3);
 else goLayer(1);
}

function showOrigPlusSelection(img){
 // Composite original + SAM result onto img
 const w=img.naturalWidth||img.width, h=img.naturalHeight||img.height;
 const c=document.createElement('canvas');c.width=w;c.height=h;
 const cx=c.getContext('2d');
 const orig=new Image();
 orig.onload=function(){
  cx.drawImage(orig,0,0,w,h);
  // Load SAM result as overlay
  const ov=new Image();
  ov.onload=function(){
   const t=document.createElement('canvas');t.width=w;t.height=h;
   const tx=t.getContext('2d');tx.drawImage(ov,0,0,w,h);
   const d=tx.getImageData(0,0,w,h);
   for(let i=0;i<d.data.length;i+=4){
    if(d.data[i]+d.data[i+1]+d.data[i+2]<600)d.data[i+3]=0;
   }
   tx.putImageData(d,0,0);
   cx.drawImage(t,0,0);
   // Also add brush overlay
   if(window._brushOv){
    const b=document.createElement('canvas');b.width=w;b.height=h;
    const bx=b.getContext('2d');bx.putImageData(window._brushOv,0,0);
    cx.drawImage(b,0,0);
   }
   img.src=c.toDataURL();
  };
  ov.src='data:image/png;base64,'+currentResult;
 };
 if(img._orig)orig.src=img._orig;
 else orig.src=img.src;
}on submitClick(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}
  const img=document.getElementById('mainImg');
  if(!img._orig)img._orig=img.src;
  currentResult=d.result_img||d.overlay;
  // Just store the result - DON'T change the image at all
  document.getElementById('overlayCanvas').style.display='none';
  // Clear previous overlay data
  window._ovData=null;
  // Enable the toggle button
  layerState=0;
  enableToggleBtn();
  // Auto-show selection on original
  showLayer(1);
  document.getElementById('toggleBtn').src='/layer1-icon.png';
  document.getElementById('toggleBtn').title='الطبقة 1';
  layerState=1;
 }catch(e){alert('خطأ: '+e.message)}
 document.getElementById('waitToast').style.display='none'}

wLayer(n){
 const img=document.getElementById('mainImg');
 const ov=document.getElementById('overlayCanvas');
 if(n==2){
  ov.style.display='none';
  img.src='data:image/png;base64,'+currentResult;
 }else if(n==1){
  // Composite SAM result + brush directly onto image
  ov.style.display='none';
  if(currentResult){
   // Wait for original to load, then composite
   if(img._orig)img.src=img._orig;
   if(img.complete){
    doComposite(img,currentResult);
   }else{
    img.onload=function(){doComposite(img,currentResult)};
   }
  }
 }else{
  ov.style.display='none';
  if(img._orig)img.src=img._orig;
 }
}

omposite(img,b64){
 const w=img.naturalWidth||img.width, h=img.naturalHeight||img.height;
 const c=document.createElement('canvas');c.width=w;c.height=h;
 const cx=c.getContext('2d');cx.drawImage(img,0,0,w,h);
 // Draw brush overlay if exists
 if(window._brushOv){
  const t=document.createElement('canvas');t.width=w;t.height=h;
  const tx=t.getContext('2d');
  tx.putImageData(window._brushOv,0,0);
  cx.drawImage(t,0,0);
 }
 // Draw SAM result overlay
 const ov=new Image();
 ov.onload=function(){
  const t=document.createElement('canvas');t.width=w;t.height=h;
  const tx=t.getContext('2d');tx.drawImage(ov,0,0,w,h);
  const d=tx.getImageData(0,0,w,h);
  for(let i=0;i<d.data.length;i+=4){
   if(d.data[i]+d.data[i+1]+d.data[i+2]<600){
    d.data[i]=0;d.data[i+1]=0;d.data[i+2]=0;d.data[i+3]=0;
   }
  }
  tx.putImageData(d,0,0);
  cx.drawImage(t,0,0);
  img.src=c.toDataURL();
  document.getElementById('toggleBtn').src='/layer1-icon.png';
 };
 ov.src='data:image/png;base64,'+b64;
}

dAndComposite(img,resultB64){
 const c=document.createElement('canvas');
 c.width=img.naturalWidth;c.height=img.naturalHeight;
 const cx=c.getContext('2d');
 cx.drawImage(img,0,0,c.width,c.height);
 const overlay=new Image();
 overlay.onload=function(){
  // Draw white selection lines from result onto original
  const t=document.createElement('canvas');
  t.width=c.width;t.height=c.height;
  const tx=t.getContext('2d');
  tx.drawImage(overlay,0,0,c.width,c.height);
  const d=tx.getImageData(0,0,c.width,c.height);
  for(let i=0;i<d.data.length;i+=4){
   if(d.data[i]+d.data[i+1]+d.data[i+2]<600){
    d.data[i+3]=0;
   }
  }
  tx.putImageData(d,0,0);
  cx.drawImage(t,0,0);
  img.src=c.toDataURL();
 };
 overlay.src='data:image/png;base64,'+resultB64;
}

function toggleView(){
 const btn=document.getElementById('toggleBtn');
 if(btn.style.opacity=='0.4')return;
 if(!currentResult)return;
 if(layerState==0){showLayer(1);btn.src='/layer1-icon.png';btn.title='الطبقة 1';layerState=1}
 else if(layerState==1){showLayer(2);btn.src='/layer2-icon.png';btn.title='الطبقة 2';layerState=2}
 else{showLayer(0);btn.src='/view-icon.png';btn.title='الأصل';layerState=0}
}

function magStart(e){
 if(e.touches.length>1)return;const t=e.touches[0];
 startTX=t.clientX;startTY=t.clientY;isScrolling=false;
 if(magTimer)clearTimeout(magTimer);
 magTimer=setTimeout(function(){magTimer=null;if(!isScrolling){magActive=true;document.getElementById('magnifier').style.display='block';updateMag(t.clientX,t.clientY)}},350)
}
function magMove(e){
 if(e.touches.length>1){if(magTimer)clearTimeout(magTimer);if(magActive){magActive=false;document.getElementById('magnifier').style.display='none'}return}
 const t=e.touches[0];
 if(magActive){
  e.preventDefault();updateMag(t.clientX,t.clientY);
  if(brushMode){
   const img=document.getElementById('mainImg'),r=img.getBoundingClientRect();
   const x=((t.clientX-r.left)/r.width)*100,y=((t.clientY-r.top)/r.height)*100;
   const ov=document.getElementById('overlayCanvas');
   if(ov.width!=img.naturalWidth||ov.height!=img.naturalHeight){
    ov.width=img.naturalWidth;ov.height=img.naturalHeight;
    ov.getContext('2d').clearRect(0,0,ov.width,ov.height);
   }
   ov.style.display='block';
   const ctx=ov.getContext('2d');
   if(!ov._bx){ov._bx=x;ov._by=y}
   ctx.beginPath();ctx.moveTo(ov._bx/100*ov.width,ov._by/100*ov.height);
   ctx.lineTo(x/100*ov.width,y/100*ov.height);
   ctx.strokeStyle='rgba(255,255,255,0.9)';ctx.lineWidth=6;ctx.lineCap='round';ctx.lineJoin='round';ctx.stroke();
   ov._bx=x;ov._by=y;
  }
 }else if(magTimer&&(Math.abs(t.clientX-startTX)>10||Math.abs(t.clientY-startTY)>10)){isScrolling=true;clearTimeout(magTimer);magTimer=null}
}
function magEnd(e){
 if(magTimer){clearTimeout(magTimer);magTimer=null;return}
 if(!magActive)return;document.getElementById('magnifier').style.display='none';magActive=false;
 if(isScrolling||!samActive)return;
 const img=document.getElementById('mainImg'),r=img.getBoundingClientRect();
 const t=e.changedTouches[0];
 const x=((t.clientX-r.left)/r.width)*100,y=((t.clientY-r.top)/r.height)*100;
 if(x<0||x>100||y<0||y>100)return;
 if(brushMode){const ov=document.getElementById('overlayCanvas');ov._bx=null;ov._by=null;window._brushOv=ov.getContext('2d').getImageData(0,0,ov.width,ov.height);enableToggleBtn();return}
 submitClick(x,y)
}
function magCancel(e){if(magTimer)clearTimeout(magTimer);magActive=false;isScrolling=false;document.getElementById('magnifier').style.display='none'}
function updateMag(mx,my){
 const img=document.getElementById('mainImg'),r=img.getBoundingClientRect(),mag=document.getElementById('magnifier'),magImg=document.getElementById('magImg');
 let mx2=mx-75,my2=my-130;if(mx2<5)mx2=5;if(mx2>window.innerWidth-155)mx2=window.innerWidth-155;if(my2<5)my2=my+50;
 mag.style.left=mx2+'px';mag.style.top=my2+'px';
 magImg.src=img.src;magImg.style.width=(r.width*2.5)+'px';magImg.style.height=(r.height*2.5)+'px';
 magImg.style.left=(-(mx-r.left)*2.5+75)+'px';magImg.style.top=(-(my-r.top)*2.5+75)+'px'
}
function downloadResult(){if(!currentResult)return;const a=document.createElement('a');a.download='result.png';a.href='data:image/png;base64,'+currentResult;a.click()}
async function undoLast(){if(stepCount<=0)return;stepCount--;document.getElementById('waitToast').style.display='block';const fd=new FormData();fd.append('step',stepCount);try{const r=await fetch('/sam_undo',{method:'POST',body:fd});const d=await r.json();if(d.error){alert(d.error);document.getElementById('waitToast').style.display='none';return}const img=document.getElementById('mainImg');const ov=document.getElementById('overlayCanvas');ov.style.display='none';window._overlayData=null;img.src='data:image/png;base64,'+(d.overlay||d.result_img);currentResult=d.overlay||d.result_img;document.getElementById('toggleBtn').src='/view-icon.png';document.getElementById('toggleBtn').title='الأصل';layerState=0}catch(e){alert('خطأ: '+e.message)}document.getElementById('waitToast').style.display='none'}
</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()
