// QR Scanner Module - Self-contained QR scanning page
// Adapted from existing camera implementation
//
// DYNAMIC PATH RESOLUTION:
// - The system now dynamically computes the correct WASM file and worker paths based on the current page location
// - This allows the emblemscanner to work in both main pages directory and subpackages
// - Paths are computed using getCurrentPages() API and stored in global data during page load
// - WASM files and workers use these dynamic paths instead of hardcoded absolute paths
//
// SCANNING MODES:
// 1. Web View Mode - Uses external camera-5.1 web interface in web-view component
//    - Fallback for devices that need special handling
//    - Configured via camera rules API (web_view: true)
//    - Redirects back with wx_redirect_to parameter
//
// 2. Plain WASM Mode - Direct WASM processing in main thread
//    - Uses qrtool.wx.js/qrtool.wx.wasm for QR detection
//    - Camera frame capture via takePhoto() API
//    - Suitable for most Android devices
//
// 3. Worker WASM Mode - WASM processing in worker thread (experimental)
//    - Uses efficient camera frame API for high-performance processing
//    - Enabled for iPhone devices (phone model contains "iphone")
//    - Reduces main thread blocking during QR processing
//
// RUNTIME CONFIGURATION:
// - Camera rules fetched from API based on phone model substring matching
// - Default behavior: Plain WASM mode for most devices
// - iPhone detection: phone_model.toLowerCase().includes('iphone') → Worker WASM
// - Special device rules: camera rules API can override with web_view: true
// - no_web_view parameter can override web_view rules for testing

// Import utility functions fromsimplify api
const {
  get_system_info,
  get_camera_rule,
  make_query,
  fetch_real_ip,
  get_tenant_id,
  is_emblem_qr_pattern,
  get_current_package_path,
  get_wasm_file_path
} = require('./libemblemscanner.js');

// Import upload functionality for verification (self-contained)
const {
  upload_image_data_urls,
  upload_image_data_urls_with_metadata,
  check_auto_torch
} = require('./upload.js');

// Import precheck utilities for image processing  
// Note: We now use our own data_url_from_frame from qrprocessor.js

// Import QR processing module
const {
  load_qrtool,
  is_qrtool_ready,
  process_frame,
  make_hint_text,
  data_url_from_frame
} = require('./qrprocessor.js');

Page({
  /**
   * Page initial data
   */
  data: {
    hint_text: '查找二维码',
    enable_debug: false,
    camera_flash: 'off',
    phone_model: 'unknown',
    zoom: -1,
    max_zoom: 1,
    show_tip: false,
    show_modal: '',
    busy: true,
    camera_sensitivity: 1,
    qrarc_class: 'sm',
    qrmarkers_class: 'hidden',
    frame_upload_interval_ms: 2000,
    return_page: '/pages/test_result_page/test_result_page', // Page to navigate to after successful scan
    server_url: 'https://themblem.com', // Default server URL
    real_ip: '', // User's real IP address
    tenant_id: '', // Tenant identifier
    userinfo: null, // User information from API
    debug_image_data_url: '',
    debug_last_result: null,
    debug_current_frame_url: '', // Current frame being processed
    logs: [], // Unified logging messages for both backend reporting and debug overlay
    debug_msgs: [], // Recent logs formatted for debug overlay (computed from logs)
    qrtool_ready: false,
    // Frame processing statistics
    frames_processed: 0,
    frames_skipped: 0,
    ok_frames: 0,
    total_processing_time: 0,
    avg_processing_time_ms: 0,
    last_frame_time_ms: 0,
    rule_zoom: -1,
    camera_rule: null,
    use_web_view: false,
    use_worker: false,
    emblem_camera_url: null,
    // State machine: loading_rules -> loading_qrtool -> init_camera -> qr_detecting -> check_auto_torch -> final_scanning -> verifying -> result
    app_state: 'loading_rules', // 'loading_rules', 'loading_qrtool', 'init_camera', 'qr_detecting', 'webview_scanning', 'check_auto_torch', 'final_scanning', 'verifying', 'result'
    scan_mode: 'unknown', // 'camera', 'webview'
    no_web_view: false, // Override web-view rule, force native camera
    worker_processing: false, // Track if worker is currently processing a frame
    verifying_stage: 0, // Verification spinner animation stage
    currentPackagePath: 'pages/emblemscanner', // Current package path for dynamic WASM loading
    wasmFilePath: '', // WASM file path for dynamic loading
    verify_resp: null // Verification response from API
  },

  onLoad(options) {
    console.log('QR Scanner module loaded', options);

    // Store page options for use in onShow
    this.pageOptions = options || {};

    const no_web_view = options.no_web_view === '1' || options.no_web_view === 'true';

    this.setData({
      return_page: options.return_page || '/pages/test_result_page/test_result_page',
      no_web_view: no_web_view
    });

    // Store current package path in page data for dynamic WASM loading
    const currentPackagePath = get_current_package_path();
    const wasmFilePath = get_wasm_file_path('qrtool.wx.wasm.br');

    this.setData({
      currentPackagePath: currentPackagePath,
      wasmFilePath: wasmFilePath
    });

    console.log('Current package path:', currentPackagePath);
    console.log('WASM file path:', wasmFilePath);

    // Log page load for backend reporting (like camera.js)
    this.log("emblemscanner page load");
    this.log("options", JSON.stringify(options));
  },

  fetchRealIP() {
    fetch_real_ip((_err, ip) => {
      this.setData({ real_ip: ip });
    });
  },

  fetchTenantID() {
    const tenant_id = get_tenant_id();
    this.setData({ tenant_id });
  },

  fetchUserInfo() {
    // Use WeChat official API to get user info
    wx.getUserInfo({
      success: (res) => {
        const userInfo = res.userInfo;
        this.setData({ userinfo: userInfo });
        this.log('User info fetched via API:', userInfo.nickName || 'unknown');
      },
      fail: (err) => {
        this.log('Failed to fetch user info:', err.errMsg);
        // Fallback to empty userinfo if API fails
        this.setData({ userinfo: {} });
      }
    });
  },

  initializeQRTool() {
    load_qrtool(this.data.wasmFilePath);
    
    const checkReady = () => {
      if (is_qrtool_ready()) {
        this.setData({ qrtool_ready: true });
        this.log('QRTool WASM loaded and ready');
        
        // Step 4: QRTool loaded, now initialize camera
        this.startCameraInit();
      } else {
        setTimeout(checkReady, 100);
      }
    };
    checkReady();
  },

  initializeSystem(enable_debug) {
    const systemInfo = get_system_info();
    const phone_model = systemInfo.model;
    const use_worker = phone_model.toLowerCase().includes("iphone");

    this.setData({
      enable_debug,
      phone_model,
      window_width: systemInfo.windowWidth,
      window_height: systemInfo.windowHeight,
      use_worker: use_worker
    });

    console.log(`Phone model: ${phone_model}, Use worker: ${use_worker}`);

    // Log system info for backend reporting (like camera.js)
    this.log("phone model:", phone_model);
    this.log("window width", systemInfo.windowWidth, "height", systemInfo.windowHeight);
    this.log("use worker:", use_worker);
  },

  /**
   * Set up worker for iPhone processing like camera.js
   */
  setupWorker() {
    // Create worker with dynamic path based on current package location
    var packagePath = this.data.currentPackagePath || 'pages/emblemscanner';
    var workerPath = `${packagePath}/worker/index.js`;
    console.log('Creating worker with path:', workerPath);

    var worker = wx.createWorker(workerPath, {
      useExperimentalWorker: true,
    });

    // Send WASM path via message (this is the only supported method)
    if (worker && this.data.wasmFilePath) {
      console.log("Sending WASM path via message:", this.data.wasmFilePath);
      worker.postMessage({
        type: "wasm_path",
        path: this.data.wasmFilePath
      });
    }

    worker.onMessage((msg) => {
      console.log('Worker message:', msg.type);

      if (msg.type === "ready") {
        console.log('Worker is ready');
        // Worker is ready to process frames
        return;
      }

      if (msg.type === "result") {
        // Clear processing flag when we get a result
        this.setData({ worker_processing: false });

        const result = msg.res;
        const processingTime = msg.processing_time;
        
        // Add WASM response to debug messages
        if (this.data.enable_debug) {
          this.log(`WASM response: ${JSON.stringify(result)}`);
        }
        
        // Update statistics
        const newFramesProcessed = this.data.frames_processed + 1;
        const newTotalTime = this.data.total_processing_time + processingTime;
        const newAvgTime = Math.round(newTotalTime / newFramesProcessed);
        
        this.setData({
          frames_processed: newFramesProcessed,
          total_processing_time: newTotalTime,
          avg_processing_time_ms: newAvgTime,
          last_frame_time_ms: Math.round(processingTime)
        });
        
        if (result) {
          // For worker, we need to trigger image collection when we find a good QR
          if (result.qrcode && is_emblem_qr_pattern(result.qrcode)) {
            this.log(`Worker QR detected: ${result.qrcode}`);

            // Check auto-torch on first valid QR detection (only when in QR detecting state)
            if (this.data.app_state === 'qr_detecting') {
              this.startCheckingAutoTorch(result.qrcode);

              // Trigger zoom-in if function is set up
              if (this.on_first_qr_found) {
                this.on_first_qr_found();
              }

              return; // Exit early, auto-torch check will return to final_scanning
            }

            if (this.data.app_state === 'final_scanning' && result.ok) {
              // Request worker to submit image data
              const worker = this.get_worker();
              if (worker) {
                worker.postMessage({ type: "ready_to_submit" });
              }
            }
          }
          
          this.handleQRResult(result, this.lastWorkerFrame);
        }
      } else if (msg.type === "submit") {
        // Handle submit message from worker (image data for upload)
        const result = msg.res;
        const imageData = msg.image_data;
        if (imageData) {
          console.log(`worker submitted image data: ${imageData.data}`);
          const uca = new Uint8ClampedArray(imageData.data);
          console.log(`first 4 bytes of image data in message: ${uca[0]}, ${uca[1]}, ${uca[2]}, ${uca[3]}`);
          const dataUrl = data_url_from_frame(imageData.width, imageData.height, uca);
          this.updateDebugFrameUrls(dataUrl);
          this.image_data_urls.push(dataUrl);

          // Update ok frames counter
          this.setData({
            ok_frames: this.image_data_urls.length
          });

          if (this.image_data_urls.length >= 3) {
            this.log('3 good images collected via worker, starting verification');
            this.startVerifying();
            this.submitImageForVerification(this.image_data_urls, result.qrcode);
            this.image_data_urls = []; // Reset for next scan
            this.setData({ ok_frames: 0 }); // Reset counter
          } else {
            this.log(`Collected ${this.image_data_urls.length}/3 worker images`);
          }
        }
      }
    });
    
    this.log('Worker set up for iPhone processing');
    return worker;
  },

  loadCameraRules(max_zoom = null) {
    get_camera_rule(max_zoom, (rule) => {
      console.log('Camera rule loaded:', rule);
      
      const should_use_webview = rule.web_view && !this.data.no_web_view;
      let emblem_camera_url = null;
      
      if (should_use_webview) {
        emblem_camera_url = "https://themblem.com/camera-5.1/?" + make_query(rule.zoom, this.data.return_page, this.data.real_ip, this.data.tenant_id);
        this.log(`Using web-view camera: ${emblem_camera_url}`);
      } else {
        this.log('Using native camera with local WASM processing');
      }
      
      this.setData({
        camera_rule: rule,
        zoom: rule.zoom,
        rule_zoom: rule.zoom,
        camera_sensitivity: rule.sensitivity || 1,
        use_web_view: should_use_webview,
        emblem_camera_url: emblem_camera_url
      });
      
      this.log(`Camera rule: zoom=${rule.zoom}, web_view=${rule.web_view}${this.data.no_web_view ? ' (NO_WEB_VIEW)' : ''}`);
      
      if (should_use_webview) {
        // Step 3a: Go directly to webview scanning (no QRTool needed)
        this.startWebviewScanning();
      } else {
        // Step 3b: Load QRTool for camera mode
        this.log('Starting QRTool initialization');
        this.transitionToState('loading_qrtool');
        this.initializeQRTool();
      }
    });
  },

  onCameraReady(e) {
    console.log('Camera ready', e);
    
    // Get max zoom from camera initialization
    let max_zoom = null;
    if (e && e.detail && e.detail.maxZoom) {
      max_zoom = e.detail.maxZoom;
      this.setData({
        max_zoom: max_zoom
      });
      this.log(`Camera max zoom: ${max_zoom}`);
    }
    
    this.log("onCameraReady: create camera context");
    this.camera_context = wx.createCameraContext();
    this.log('Camera context created');
    
    // Step 5: Set up initial zoom and start scanning
    if (this.data.camera_rule) {
      this.setupCameraZoom(this.data.camera_rule);
      this.log('Initial zoom set up');
    }
    
    // Step 6: Transition to scanning (which also starts frame processing)
    this.startCameraScanning();
  },

  onCameraError(e) {
    console.error('Camera error', e);
    this.log(`Camera error: ${JSON.stringify(e.detail)}`);
    // Redirect with failure instead of showing modal
    this.goToResult(null, null, false);
  },

  toggle_torch() {
    const newFlash = this.data.camera_flash === 'torch' ? 'off' : 'torch';
    this.setData({
      camera_flash: newFlash
    });
    console.log('Torch toggled to:', newFlash);
  },


  show_scanguide() {
    this.setData({
      show_modal: 'scanguide',
      show_tip: false
    });
  },

  show_service() {
    this.setData({
      show_modal: 'service'
    });
  },

  restart_camera() {
    this.restartScanning();
  },

  close_modal() {
    this.setData({
      show_modal: ''
    });
  },

  debug_tap() {
    const count = (this.debug_tap_count || 0) + 1;
    this.debug_tap_count = count;
    
    if (count >= 5) {
      this.setData({
        enable_debug: !this.data.enable_debug
      });
      this.debug_tap_count = 0;
    }
    
    setTimeout(() => {
      this.debug_tap_count = 0;
    }, 3000);
  },


  /**
   * Set up camera zoom functionality similar to camera.js
   */
  setupCameraZoom(rule) {
    if (!this.camera_context) {
      this.log('Cannot setup zoom: camera context not ready');
      return;
    }
    
    console.log('setupCameraZoom called:', rule);
    const zoom = rule.zoom;
    const initial_zoom = 2;
    
    this.setData({
      zoom: initial_zoom,
      rule_zoom: zoom
    });
    
    this.log(`Camera set initial zoom to ${initial_zoom}x, will zoom in to ${zoom}x when QR is found`);
    this.camera_context.setZoom({ zoom: initial_zoom });
    
    // Set up zoom-in behavior when QR is found
    this.on_first_qr_found = () => {
      this.log(`First QR found, zoom to ${zoom}x`);
      this.camera_context.setZoom({ zoom: zoom });
      this.setData({
        zoom: zoom,
        qrmarkers_class: '',
        qrarc_class: 'lg'
      });
    };
  },

  /**
   * State Machine: Transition to new state
   */
  transitionToState(newState, mode = null) {
    const oldState = this.data.app_state;
    this.log(`State: ${oldState} -> ${newState}${mode ? ` (${mode})` : ''}`);
    
    const stateData = { app_state: newState };
    if (mode) stateData.scan_mode = mode;
    
    this.setData(stateData);
  },

  /**
   * State: Loading -> Init Camera (prepare camera context)
   */
  startCameraInit() {
    this.transitionToState('init_camera', 'camera');
    this.setData({
      hint_text: '初始化相机...',
      busy: true
    });
  },

  /**
   * State: Init Camera -> QR Detecting (Camera)
   * Also starts frame processing since all conditions are implied to be ready
   */
  startCameraScanning() {
    this.transitionToState('qr_detecting', 'camera');
    this.setData({
      hint_text: '查找二维码',
      busy: false
    });
    
    // Start frame processing - all conditions are implied to be ready at this point
    this.log('Starting camera frame listener');
    this.lastFrameTime = 0;
    
    // Set up camera frame listener
    this.listener = this.camera_context.onCameraFrame((frame) => {
      this.onCameraFrame(frame);
    });

    // Start the listener with worker if using worker mode
    var worker = this.data.use_worker ? this.get_worker() : null;
    this.listener.start({
      worker,
    });
  },


  /**
   * Camera frame callback - receives live camera frames
   */
  onCameraFrame(frame) {
    // Only process frames when in QR detecting or final scanning states
    if (this.data.app_state !== 'qr_detecting' && this.data.app_state !== 'final_scanning') {
      return;
    }

    // Throttle frame processing to avoid overwhelming the system
    const now = Date.now();
    if (this.lastFrameTime && (now - this.lastFrameTime) < 100) {
      this.setData({
        frames_skipped: this.data.frames_skipped + 1
      });
      return;
    }
    this.lastFrameTime = now;

    // Use worker for iPhone, direct processing for other devices
    if (this.data.use_worker) {
      // Skip if worker is already processing a frame
      if (this.data.worker_processing) {
        this.setData({
          frames_skipped: this.data.frames_skipped + 1
        });
        return;
      }

      // Worker processing (iPhone)
      this.lastWorkerFrame = frame; // Store for handleQRResult

      // Copy frame data to avoid TOCTOU race condition (like camera.js)
      var uca1 = new Uint8ClampedArray(frame.data);
      var uca = new Uint8ClampedArray(uca1);

      // Generate debug frame data URL if debug is enabled
      if (this.data.enable_debug) {
        const frameDataUrl = data_url_from_frame(frame.width, frame.height, uca);
        this.updateDebugFrameUrls(frameDataUrl);
      }

      // Set processing flag before sending message
      this.setData({ worker_processing: true });

      const worker = this.get_worker();
      if (worker) {
        worker.postMessage({
          type: 'frame',
          width: frame.width,
          height: frame.height,
          camera_sensitivity: this.data.camera_sensitivity
        });
      }
    } else {
      // Direct processing (other devices)
      this.processFrameDirect(frame);
    }
  },

  /**
   * Process frame directly (non-iPhone devices)
   */
  processFrameDirect(frame) {
    const processStart = Date.now();

    try {
      // Copy frame data to avoid TOCTOU race condition (like camera.js)
      var uca1 = new Uint8ClampedArray(frame.data);
      var uca = new Uint8ClampedArray(uca1);
      
      // Generate debug frame data URL if debug is enabled
      if (this.data.enable_debug) {
        const frameDataUrl = data_url_from_frame(frame.width, frame.height, uca);
        this.updateDebugFrameUrls(frameDataUrl);
      }
      
      const result = process_frame(frame.width, frame.height, uca, this.data.camera_sensitivity, this.data.enable_debug);
      
      // Calculate processing time
      const processEnd = Date.now();
      const processingTime = processEnd - processStart;
      
      // Update statistics
      const newFramesProcessed = this.data.frames_processed + 1;
      const newTotalTime = this.data.total_processing_time + processingTime;
      const newAvgTime = Math.round(newTotalTime / newFramesProcessed);
      
      this.setData({
        frames_processed: newFramesProcessed,
        total_processing_time: newTotalTime,
        avg_processing_time_ms: newAvgTime,
        last_frame_time_ms: Math.round(processingTime),
        debug_last_result: result
      });
      
      if (result) {
        this.handleQRResult(result, frame, uca);
      }
    } catch (error) {
      this.log(`Frame processing error: ${error.message}`);
      // Still count failed processing attempts
      const processEnd = Date.now();
      const processingTime = processEnd - processStart;
      
      const newFramesProcessed = this.data.frames_processed + 1;
      const newTotalTime = this.data.total_processing_time + processingTime;
      const newAvgTime = Math.round(newTotalTime / newFramesProcessed);
      
      this.setData({
        frames_processed: newFramesProcessed,
        total_processing_time: newTotalTime,
        avg_processing_time_ms: newAvgTime,
        last_frame_time_ms: Math.round(processingTime),
        debug_last_result: null
      });
    }
  },

  /**
   * Handle QR processing result
   */
  handleQRResult(result, frame, copiedFrameData = null) {
    // Update debug info - prioritize WASM debug image, fallback to original frame
    if (this.data.enable_debug) {
      if (result.debug_data_url) {
        // Priority 1: WASM returned a processed debug image (with QR detection overlay, etc.)
        this.setData({
          debug_image_data_url: result.debug_data_url
        });
      } else if (this.data.debug_current_frame_url) {
        // Priority 2: No WASM debug image available, show the raw camera frame
        this.setData({
          debug_image_data_url: this.data.debug_current_frame_url
        });
      }
      // If neither exists, the debug image box will remain empty/previous image
    }
    
    // Generate hint text
    const hint = make_hint_text(result);
    
    // Check if we have a valid QR code that's ready for upload (like camera.js)
    // don't require ok as we only care about the view has a valid qrcode in it
    // zooming in so that it's more likely to be clear enough for upload
    if (result.qrcode && is_emblem_qr_pattern(result.qrcode)) {
      this.log(`QR detected: ${result.qrcode} ok: ${result.ok}: err ${result.err}`);

      // Check auto-torch on first valid QR detection (only when in QR detecting state)
      if (this.data.app_state === 'qr_detecting') {
        this.startCheckingAutoTorch(result.qrcode);

        // Trigger zoom-in if function is set up
        if (this.on_first_qr_found) {
          this.on_first_qr_found();
        }

        return; // Exit early, auto-torch check will return to final_scanning state
      }

      if (this.data.app_state === 'final_scanning' && result.ok) {
        this.onQRCodeDetected(result.qrcode, frame, copiedFrameData); // Pass the copied frame data
      }
    } else {
      // Update hint for user guidance
      this.setData({ hint_text: hint });

      if (result.qrcode) {
        this.log(`QR found but not valid: ${result.qrcode} (${result.err})`);
      }
    }
  },

  /**
   * Handle successful QR code detection - collect images from "ok" frames and verify (non-worker case)
   */
  onQRCodeDetected(qrCode, frameData, copiedFrameData = null) {
    // Only collect images for non-worker case (worker handles its own image collection)
    if (!this.data.use_worker && frameData && copiedFrameData) {
      // Convert frame data to data URL for upload (only called for "ok" frames)
      // Use the copied frame data instead of original frameData.data to avoid corruption
      const dataUrl = data_url_from_frame(frameData.width, frameData.height, copiedFrameData);
      this.image_data_urls.push(dataUrl);
      
      // Update ok frames counter
      this.setData({
        ok_frames: this.image_data_urls.length
      });
      
      this.log(`Collected ${this.image_data_urls.length}/3 good images (direct) - using copied data`);
      
      // Add debug info about the submitted image
      if (this.data.enable_debug) {
        this.log(`Submitted image preview: ${dataUrl.substring(0, 50)}...`);
      }
      
      // Need 3 "ok" frames before verification (like camera.js)
      if (this.image_data_urls.length >= 3) {
        this.log('3 good images collected, starting verification');
        this.startVerifying();
        this.submitImageForVerification(this.image_data_urls, qrCode);
        this.image_data_urls = []; // Reset for next scan
        this.setData({ ok_frames: 0 }); // Reset counter
      }
    }
    // For worker case, image collection is handled in setupWorker() message handler
    // If less than 3 images, continue scanning to collect more
  },

  /**
   * Submit images for verification like camera.js
   */
  submitImageForVerification(dataUrls, qrCode) {
    this.log('Submitting images for verification');
    const begin = Date.now();

    const success = (res) => {
      this.log(`Upload success, code: ${res.statusCode}`);
      if (res.statusCode === 200) {
        let resp;
        if (typeof res.data === "string") {
          resp = JSON.parse(res.data);
        } else {
          resp = res.data;
        }

        // Store verification response in page data
        this.setData({ verify_resp: resp });

        if (resp.serial_code) {
          // Let verification animation run for a bit, then redirect with success
          const delay = 3000 - (Date.now() - begin);
          setTimeout(() => {
            this.goToResult(qrCode, resp.serial_code, true);
          }, delay > 0 ? delay : 0);
        } else {
          // Redirect with failure instead of showing modal
          const delay = 3000 - (Date.now() - begin);
          setTimeout(() => {
            this.goToResult(qrCode, null, false);
          }, delay > 0 ? delay : 0);
        }
      } else {
        // Redirect with failure instead of showing modal
        this.goToResult(qrCode, null, false);
      }
    };

    const fail = (e) => {
      this.log(`Upload failed: ${JSON.stringify(e)}`);
      // Redirect with failure instead of showing modal
      this.goToResult(qrCode, null, false);
    };

    // Use enhanced upload function with local metadata (no global data needed)
    const metadata = {
      real_ip: this.data.real_ip,
      phone_model: this.data.phone_model,
      server_url: this.data.server_url,
      userinfo: this.data.userinfo
    };

    upload_image_data_urls_with_metadata(dataUrls, qrCode, metadata, success, fail, this.data.logs.join("\n"), null);
  },


  /**
   * State: Loading Rules -> Webview Scanning
   */
  startWebviewScanning() {
    this.transitionToState('webview_scanning', 'webview');
    this.setData({
      hint_text: '查找二维码',
      busy: false
    });
  },

  /**
   * State: QR Detecting -> Check Auto-torch
   */
  startCheckingAutoTorch(qrcode) {
    this.transitionToState('check_auto_torch');
    this.setData({
      hint_text: '检查补光设置...'
    });

    check_auto_torch(qrcode, this.data.server_url, (auto_torch, camera_sensitivity) => {
      this.log(`Auto-torch check: ${auto_torch}, sensitivity: ${camera_sensitivity}`);

      if (auto_torch) {
        this.setData({
          camera_flash: 'torch'
        });
        this.log('Auto-torch enabled');
      }

      // Update camera sensitivity and continue scanning
      this.setData({
        camera_sensitivity: camera_sensitivity || this.data.camera_sensitivity
      });

      // Transition to final_scanning state (ready to collect frames for verification)
      this.transitionToState('final_scanning');
      this.setData({
        hint_text: '查找二维码'
      });
    });
  },


  /**
   * State: Final Scanning -> Verifying (show spinner while waiting for backend)
   */
  startVerifying() {
    this.transitionToState('verifying');
    this.setData({
      hint_text: '识别成功',
      show_modal: 'verifying',
      verifying_stage: 0
    });

    // Start verification animation like verifyspin component
    setTimeout(() => {
      this.setData({
        verifying_stage: 1
      });
    }, 3000);
  },

  /**
   * State: Any -> Result (jump to return page)
   */
  goToResult(qrCode, serialCode, success = true) {
    this.transitionToState('result');

    // Always redirect to return_page, pass ok=1 for success, ok=0 for failure
    // Only pass qr_code and serial_code if ok=1 (success)
    let url;
    if (success && qrCode) {
      if (serialCode) {
        url = `${this.data.return_page}?ok=1&qr_code=${encodeURIComponent(qrCode)}&serial_code=${encodeURIComponent(serialCode)}`;
      } else {
        url = `${this.data.return_page}?ok=1&qr_code=${encodeURIComponent(qrCode)}`;
      }
    } else {
      url = `${this.data.return_page}?ok=0`;
    }

    wx.redirectTo({
      url: url,
      success: () => {
        this.log(`Navigated to: ${this.data.return_page}`);
      },
      fail: (err) => {
        this.log(`Navigation failed: ${err.errMsg}`);
        this.restart_camera();
      }
    });
  },

  /**
   * State: Any -> Loading (restart)
   */
  restart_camera() {
    // Go to initializing if QRTool isn't ready, otherwise loading
    const newState = this.data.qrtool_ready ? 'loading' : 'initializing';
    this.transitionToState(newState);
    
    const hintText = this.data.qrtool_ready ? '初始化相机...' : '初始化QR工具...';
    this.setData({
      show_modal: '',
      hint_text: hintText,
      busy: true,
      // Reset frame processing statistics
      frames_processed: 0,
      frames_skipped: 0,
      ok_frames: 0,
      total_processing_time: 0,
      avg_processing_time_ms: 0,
      last_frame_time_ms: 0
    });
    
    // Reset frame timing
    this.lastFrameTime = 0;
    
    // Reload camera rules to restart the flow (only if QRTool is ready)
    if (this.data.qrtool_ready) {
      this.loadCameraRules();
    }
  },

  /**
   * Unified logging function for both backend reporting and debug overlay
   */
  log(...what) {
    const message = what.join(" ");

    // Always log to console
    console.log(...what);

    // Always add to backend logs for reporting
    const newLogs = this.data.logs.concat([new Date() + ": " + message]);

    // Update debug overlay with recent logs if debug is enabled
    let debugMsgs = [];
    if (this.data.enable_debug) {
      // Show only the 5 most recent logs, formatted with short timestamp
      debugMsgs = newLogs.slice(-5).reverse().map(log => {
        const timestamp = new Date().toLocaleTimeString();
        const logMessage = log.substring(log.indexOf(": ") + 2); // Remove full date, keep message
        return `${timestamp}: ${logMessage}`;
      });
    }

    this.setData({
      logs: newLogs,
      debug_msgs: debugMsgs
    });
  },

  /**
   * Update debug frame URLs for visualization
   */
  updateDebugFrameUrls(frameDataUrl) {
    if (!this.data.enable_debug) return;
    
    // Store current frame URL for potential use as fallback
    this.setData({
      debug_current_frame_url: frameDataUrl
    });
  },

  /**
   * Handle messages from web-view camera
   */
  on_webview_message(e) {
    console.log('Web-view message received:', e);
    this.log(`Web-view message: ${JSON.stringify(e.detail)}`);
    
    // Handle QR code results from web-view
    if (e.detail && e.detail.data && e.detail.data.length > 0) {
      const messageData = e.detail.data[0];
      if (messageData.qr_code) {
        // Web-view results go directly to result (no verification step)
        this.goToResult(messageData.qr_code, messageData.serial_code, true);
      } else if (messageData.error) {
        this.log(`Web-view error: ${messageData.error}`);
        // Redirect with failure instead of showing hint
        this.goToResult(null, null, false);
      }
    }
  },

  /**
   * Check if QR code matches Emblem pattern
   */
  isEmblemQRPattern(qrCode) {
    return is_emblem_qr_pattern(qrCode);
  },

  /**
   * Lifecycle - page show
   */
  onShow() {
    console.log('EmblemScanner page shown - initializing');

    // Log page show for backend reporting (like camera.js)
    this.log("emblemscanner page show");

    // Reset state machine to initial state
    this.setData({
      app_state: 'loading_rules',
      scan_mode: 'unknown',
      busy: true,
      hint_text: '查找二维码',
      show_modal: '',
      worker_processing: false,
      verifying_stage: 0,
      // Reset frame processing statistics
      frames_processed: 0,
      frames_skipped: 0,
      ok_frames: 0,
      total_processing_time: 0,
      avg_processing_time_ms: 0,
      last_frame_time_ms: 0,
      // Clear logs and debug messages
      logs: [],
      debug_msgs: [],
      // Clear userinfo
      userinfo: null
    });

    // Initialize image collection for verification
    this.image_data_urls = [];

    const enable_debug = this.pageOptions.debug || this.pageOptions.scene == 'debug' || false;

    // Step 1: Initialize system and get phone model
    this.initializeSystem(enable_debug);
    this.fetchRealIP();
    this.fetchTenantID();
    this.fetchUserInfo();

    // Step 2: Load camera rules based on phone model
    this.loadCameraRules();
  },

  /**
   * Lifecycle - page hide
   */
  onHide() {
    console.log('EmblemScanner page hidden - cleaning up');
    this.cleanupListener();

    // Reset worker processing state to prevent stale state
    this.setData({
      worker_processing: false,
      app_state: 'loading_rules',
      busy: true
    });

    // Clear frame timing and image collection
    this.lastFrameTime = 0;
    this.image_data_urls = [];

    // Clear logs to prevent memory buildup
    this.setData({
      logs: [],
      debug_msgs: []
    });

    // Clear zoom function reference
    this.on_first_qr_found = null;
  },

  /**
   * Lifecycle - page unload
   */
  onUnload() {
    this.cleanupListener();
    this.terminateWorker();
  },

  /**
   * Clean up camera frame listener and camera context
   */
  cleanupListener() {
    if (this.listener) {
      this.log("cleanupListener: stop camera frame listener");
      this.listener.stop();
      this.listener = null;
      this.log('Camera frame listener stopped');
    }

    // Clear camera context to prevent multiple camera elements
    if (this.camera_context) {
      this.camera_context = null;
      this.log('Camera context cleared');
    }

    // Clean up worker state
    this.cleanupWorkerState();
  },

  get_worker() {
    if (!this.worker && this.data.use_worker) {
      this.worker = this.setupWorker();
    }
    return this.worker;
  },

  /**
   * Clean up worker state without terminating the worker
   */
  cleanupWorkerState() {
    if (this.data.use_worker) {
      // Reset worker processing flag
      this.setData({
        worker_processing: false
      });

      // Clear stored worker frame reference
      this.lastWorkerFrame = null;

      this.log('Worker state cleaned up');
    }
  },

  /**
   * Terminate and clean up worker on page unload
   */
  terminateWorker() {
    if (this.worker) {
      this.worker.terminate();
      this.worker = null;
      this.log('Worker terminated');
    }
  }

});