const fs = require('fs');
const path = require('path');
const vm = require('vm');

const root = __dirname;
const viewer = fs.readFileSync(path.join(root, 'web', 'viewer.html'), 'utf8');
const capture = fs.readFileSync(path.join(root, 'yjm_win2rtc_capture.py'), 'utf8');

function ok(name, condition) {
  if (!condition) {
    console.error('FAIL', name);
    process.exitCode = 1;
  } else {
    console.log('OK', name);
  }
}

function extractFunction(source, name) {
  const start = source.indexOf(`function ${name}(`);
  if (start < 0) throw new Error(`missing function ${name}`);
  const brace = source.indexOf('{', start);
  let depth = 0;
  let quote = '';
  let escaped = false;
  for (let i = brace; i < source.length; i++) {
    const ch = source[i];
    if (quote) {
      if (escaped) escaped = false;
      else if (ch === '\\') escaped = true;
      else if (ch === quote) quote = '';
      continue;
    }
    if (ch === '"' || ch === "'" || ch === '`') { quote = ch; continue; }
    if (ch === '{') depth++;
    if (ch === '}') {
      depth--;
      if (depth === 0) return source.slice(start, i + 1);
    }
  }
  throw new Error(`unterminated function ${name}`);
}

const panAxisBounds = vm.runInNewContext(`(${extractFunction(viewer, 'panAxisBounds')})`);
const large = panAxisBounds(300, 1000);
const small = panAxisBounds(300, 200);
ok('1:1 large canvas reaches both edges without losing all content', large.min === -350 && large.max === 350);
ok('fit small canvas can move inside stage but cannot disappear', small.min === -50 && small.max === 50);

ok('viewer build v141', viewer.includes("const BUILD_TAG='v141'") && viewer.includes('RTC8790_8791 viewer v141') && viewer.includes('RTC v141'));
ok('fit and 1:1 both use translate pan', viewer.includes('canvas.style.transform=`translate(${Math.round(viewPanX)}px,${Math.round(viewPanY)}px)`') && !viewer.includes("canvas.style.transform=fit?'':"));
ok('two finger stage pan no fit-only gate', viewer.includes("stageEl.addEventListener('touchmove'") && viewer.includes('setViewPan(stagePanStart.panX+(cx-stagePanStart.cx),stagePanStart.panY+(cy-stagePanStart.cy))') && !viewer.includes('if(!fit){viewPanX=stagePanStart.panX'));
ok('pan is clamped for fit and 1:1', viewer.includes('function clampViewPan()') && viewer.includes('function viewPanBounds()') && viewer.includes('panAxisBounds'));
ok('pointer find buttons are removed', !viewer.includes('id="pointerFindBtn"') && !viewer.includes('id="mobilePointerFindBtn"') && !viewer.includes('data-cmd="/cursor"'));
ok('internal cursor response handling remains diagnostic', viewer.includes('if(data.cursor) applyCursorState(data.cursor,true)') && viewer.includes('function applyCursorState(cur,focus)'));
ok('cursor response updates crosshair and 1:1 view only', viewer.includes('remotePointer.x=fx; remotePointer.y=fy; mobilePointerReady=true;') && viewer.includes('if(focus&&!fit) centerViewOnRemotePoint(fx,fy)'));
ok('outside capture is explicit failure', viewer.includes('cur.inside_capture!==false') && viewer.includes('현재 Windows 포인터가 캡처 화면 밖에 있습니다'));
ok('host cursor response includes capture membership', capture.includes('"inside_capture": bool(inside_capture)') && capture.includes('left <= int(x) < left + width') && capture.includes('top <= int(y) < top + height'));
ok('host build v141', capture.includes('BUILD_VERSION = "2.3.8.141"') && capture.includes('BUILD_TAG = "v141"'));

if (!process.exitCode) console.log('OK test_rtc8790_mobile_pan_pointer_v205');
