/* Pyrk native device bridge v2
 * Additive Capacitor integration for camera, GPS, QR scanning and push registration.
 * Web/browser behavior remains available as a fallback when native plugins are absent.
 */
(function () {
  'use strict';

  if (window.PyrkNativeDevice) return;

  const listenerHandles = [];
  let pushSetupStarted = false;
  let lastPushToken = '';
  let nativeWatchSeq = 0;
  const nativeWatchIds = new Map();

  function capacitor() {
    return window.Capacitor || null;
  }

  function isNativeApp() {
    try {
      const cap = capacitor();
      if (cap && typeof cap.isNativePlatform === 'function' && cap.isNativePlatform()) return true;
      if (cap && cap.platform && cap.platform !== 'web') return true;
      if (/(?:^|[?&])(?:app|source|native_app|capacitor|app_shell)=(?:1|app|native|android|ios)(?:&|$)/i.test(location.search || '')) return true;
      if (/PyrkApp|Capacitor/i.test(navigator.userAgent || '')) return true;
      return sessionStorage.getItem('pyrk_native_app') === '1' || localStorage.getItem('pyrk_native_app') === '1';
    } catch (error) {
      return false;
    }
  }

  function plugin(name) {
    const cap = capacitor();
    if (!cap || !cap.Plugins) return null;
    return cap.Plugins[name] || null;
  }

  function getPlatform() {
    try {
      const cap = capacitor();
      if (cap && typeof cap.getPlatform === 'function') return String(cap.getPlatform() || 'web').toLowerCase();
      if (cap && cap.platform) return String(cap.platform).toLowerCase();
    } catch (error) {}
    return 'web';
  }

  function isAvailable(feature) {
    if (!isNativeApp()) return false;
    const map = {
      camera: 'Camera',
      geolocation: 'Geolocation',
      qr: 'CapacitorBarcodeScanner',
      push: 'PushNotifications',
      device: 'Device',
      app: 'App',
      filesystem: 'Filesystem'
    };
    const name = map[feature] || feature;
    return !!plugin(name);
  }

  function safeMessage(error, fallback) {
    if (!error) return fallback || 'Native feature failed.';
    if (typeof error === 'string') return error;
    return error.message || error.error || fallback || 'Native feature failed.';
  }

  function permissionGranted(value) {
    return value === 'granted' || value === 'limited';
  }

  function base64ToBlob(base64, mime) {
    const clean = String(base64 || '').replace(/^data:[^;]+;base64,/, '');
    const bytes = atob(clean);
    const buffer = new Uint8Array(bytes.length);
    for (let i = 0; i < bytes.length; i++) buffer[i] = bytes.charCodeAt(i);
    return new Blob([buffer], {type: mime || 'image/jpeg'});
  }

  function extensionFromFormat(format) {
    const value = String(format || 'jpeg').toLowerCase();
    if (value === 'jpg' || value === 'jpeg') return 'jpg';
    if (value === 'png') return 'png';
    if (value === 'webp') return 'webp';
    return 'jpg';
  }

  function mimeFromFormat(format) {
    const ext = extensionFromFormat(format);
    return ext === 'jpg' ? 'image/jpeg' : `image/${ext}`;
  }

  async function mediaResultToFile(result, prefix) {
    if (!result) throw new Error('The camera did not return a photo.');
    const format = result.metadata && result.metadata.format ? result.metadata.format : (result.format || 'jpeg');
    const mime = mimeFromFormat(format);
    const ext = extensionFromFormat(format);
    const filename = `${prefix || 'pyrk-native-photo'}-${Date.now()}.${ext}`;

    const nativeUri = result.uri || result.path || '';
    if (nativeUri && isAvailable('filesystem')) {
      try {
        const fs = plugin('Filesystem');
        const read = await fs.readFile({path: nativeUri});
        if (read && typeof read.data === 'string') {
          return new File([base64ToBlob(read.data, mime)], filename, {type: mime, lastModified: Date.now()});
        }
        if (read && read.data instanceof Blob) {
          return new File([read.data], filename, {type: read.data.type || mime, lastModified: Date.now()});
        }
      } catch (error) {
        // Continue to webPath/thumbnail fallback without disrupting the photo flow.
      }
    }

    if (result.webPath) {
      try {
        const response = await fetch(result.webPath);
        if (response.ok) {
          const blob = await response.blob();
          return new File([blob], filename, {type: blob.type || mime, lastModified: Date.now()});
        }
      } catch (error) {
        // Continue to thumbnail fallback.
      }
    }

    const base64Preview = result.thumbnail || result.base64String || result.dataUrl || '';
    if (base64Preview) {
      return new File([base64ToBlob(base64Preview, mime)], filename, {type: mime, lastModified: Date.now()});
    }

    throw new Error('The captured photo could not be prepared for upload.');
  }

  async function ensureCameraPermission() {
    const camera = plugin('Camera');
    if (!camera) throw new Error('Native camera is not available in this app build.');
    if (typeof camera.checkPermissions !== 'function') return;
    let status = await camera.checkPermissions();
    if (!permissionGranted(status.camera)) {
      status = await camera.requestPermissions({permissions: ['camera']});
    }
    if (!permissionGranted(status.camera)) throw new Error('Camera permission was not granted.');
  }

  async function capturePhoto(options) {
    const camera = plugin('Camera');
    if (!camera || !isNativeApp()) throw new Error('Native camera is not available.');
    await ensureCameraPermission();

    const opts = options || {};
    const direction = opts.direction === 'front' ? 'FRONT' : 'REAR';
    let result = null;

    if (typeof camera.takePhoto === 'function') {
      result = await camera.takePhoto({
        quality: Number(opts.quality || 92),
        targetWidth: Number(opts.targetWidth || 2048),
        targetHeight: Number(opts.targetHeight || 2048),
        correctOrientation: true,
        saveToGallery: false,
        cameraDirection: direction,
        editable: 'no',
        presentationStyle: 'fullscreen',
        includeMetadata: true
      });
      const file = await mediaResultToFile(result, opts.direction === 'front' ? 'pyrk-selfie' : 'pyrk-camera');
      return {file, webPath: result.webPath || '', metadata: result.metadata || {}, source: 'native_camera'};
    }

    // Compatibility fallback for an older Capacitor camera build.
    if (typeof camera.getPhoto === 'function') {
      const legacy = await camera.getPhoto({
        quality: Number(opts.quality || 92),
        resultType: 'base64',
        source: 'CAMERA',
        direction,
        correctOrientation: true,
        saveToGallery: false
      });
      if (!legacy || !legacy.base64String) throw new Error('The native camera returned no photo.');
      const format = legacy.format || 'jpeg';
      const mime = mimeFromFormat(format);
      const ext = extensionFromFormat(format);
      const file = new File([base64ToBlob(legacy.base64String, mime)], `pyrk-camera-${Date.now()}.${ext}`, {type: mime, lastModified: Date.now()});
      return {file, webPath: legacy.webPath || '', metadata: {format}, source: 'native_camera'};
    }

    throw new Error('This app build does not include a compatible camera plugin.');
  }

  async function ensureLocationPermission() {
    const geo = plugin('Geolocation');
    if (!geo) throw new Error('Native location is not available in this app build.');
    if (typeof geo.checkPermissions !== 'function') return;
    let status = await geo.checkPermissions();
    if (!permissionGranted(status.location) && !permissionGranted(status.coarseLocation)) {
      status = await geo.requestPermissions({permissions: ['location', 'coarseLocation']});
    }
    if (!permissionGranted(status.location) && !permissionGranted(status.coarseLocation)) {
      throw new Error('Location permission was not granted.');
    }
  }

  async function getCurrentPosition(options) {
    const geo = plugin('Geolocation');
    if (!geo || !isNativeApp()) throw new Error('Native location is not available.');
    await ensureLocationPermission();
    return await geo.getCurrentPosition({
      enableHighAccuracy: options && options.enableHighAccuracy !== undefined ? !!options.enableHighAccuracy : true,
      timeout: Number((options && options.timeout) || 15000),
      maximumAge: Number((options && options.maximumAge) || 5000)
    });
  }

  async function watchPosition(success, failure, options) {
    const geo = plugin('Geolocation');
    if (!geo || !isNativeApp()) throw new Error('Native location is not available.');
    await ensureLocationPermission();
    const localId = `pyrk-native-watch-${++nativeWatchSeq}`;
    const nativeId = await geo.watchPosition({
      enableHighAccuracy: options && options.enableHighAccuracy !== undefined ? !!options.enableHighAccuracy : true,
      timeout: Number((options && options.timeout) || 15000),
      maximumAge: Number((options && options.maximumAge) || 3000),
      minimumUpdateInterval: Number((options && options.minimumUpdateInterval) || 3000)
    }, function (position, error) {
      if (error) {
        if (typeof failure === 'function') failure(error);
        return;
      }
      if (position && typeof success === 'function') success(position);
    });
    nativeWatchIds.set(localId, nativeId);
    return localId;
  }

  async function clearWatch(localId) {
    const geo = plugin('Geolocation');
    const nativeId = nativeWatchIds.get(localId) || localId;
    nativeWatchIds.delete(localId);
    if (geo && nativeId !== undefined && nativeId !== null && typeof geo.clearWatch === 'function') {
      await geo.clearWatch({id: nativeId});
    }
  }

  async function scanQr(options) {
    const scanner = plugin('CapacitorBarcodeScanner');
    if (!scanner || !isNativeApp()) throw new Error('Native QR scanner is not available in this app build.');
    const opts = options || {};
    const result = await scanner.scanBarcode({
      hint: 0,
      scanInstructions: opts.instructions || 'Place the Pyrk QR code inside the frame.',
      scanButton: true,
      scanText: opts.scanText || 'Scan QR',
      cameraDirection: 1,
      scanOrientation: 1,
      cancelButtonAccessibilityLabel: 'Cancel QR scan',
      torchButtonOnAccessibilityLabel: 'Turn torch off',
      torchButtonOffAccessibilityLabel: 'Turn torch on',
      android: {scanningLibrary: 'mlkit'}
    });
    const value = String((result && result.ScanResult) || '').trim();
    if (!value) throw new Error('No QR code was captured.');
    return {value, format: result.format};
  }

  function sameOriginPath(value) {
    if (!value) return '';
    try {
      const target = new URL(String(value), location.origin);
      if (target.origin !== location.origin) return '';
      return target.pathname + target.search + target.hash;
    } catch (error) {
      return '';
    }
  }

  async function devicePayload(token) {
    const payload = {
      action: 'register',
      token: String(token || ''),
      platform: getPlatform() || 'unknown',
      device_id: '',
      app_version: '',
      model: '',
      os_version: ''
    };
    const device = plugin('Device');
    if (device) {
      try {
        const info = typeof device.getInfo === 'function' ? await device.getInfo() : {};
        const id = typeof device.getId === 'function' ? await device.getId() : {};
        payload.device_id = String((id && id.identifier) || '');
        payload.model = String((info && (info.model || info.name)) || '');
        payload.os_version = String((info && info.osVersion) || '');
        payload.platform = String((info && info.platform) || payload.platform);
      } catch (error) {
        // Token registration remains valid without optional device metadata.
      }
    }
    const app = plugin('App');
    if (app && typeof app.getInfo === 'function') {
      try {
        const appInfo = await app.getInfo();
        payload.app_version = String((appInfo && (appInfo.version || appInfo.build)) || '');
      } catch (error) {
        // App version is optional metadata.
      }
    }
    return payload;
  }

  async function postDeviceToken(payload) {
    if (window.Pyrk && typeof window.Pyrk.post === 'function') {
      return await window.Pyrk.post('mobile_devices.php', payload);
    }
    const base = String(window.PYRK_API_BASE || './api').replace(/\/$/, '');
    const response = await fetch(`${base}/mobile_devices.php`, {
      method: 'POST',
      credentials: 'same-origin',
      headers: {'Content-Type': 'application/json', 'X-CSRF-Token': String(window.PYRK_CSRF || '')},
      body: JSON.stringify(payload)
    });
    return await response.json();
  }

  async function savePushToken(token) {
    token = String(token || '').trim();
    if (!token || token === lastPushToken) return;
    const response = await postDeviceToken(await devicePayload(token));
    if (!response || !response.ok) throw new Error((response && response.message) || 'Could not register this phone for push notifications.');
    lastPushToken = token;
    try { localStorage.setItem('pyrk_push_token_registered', token); } catch (error) {}
    document.dispatchEvent(new CustomEvent('pyrk:native-push-ready', {detail: {token}}));
  }

  async function setupPushNotifications(options) {
    if (pushSetupStarted) return {started: true, token: lastPushToken};
    const push = plugin('PushNotifications');
    if (!push || !isNativeApp()) return {started: false, reason: 'unavailable'};
    pushSetupStarted = true;

    try {
      if (typeof push.createChannel === 'function') {
        try {
          await push.createChannel({
            id: 'pyrk_general',
            name: 'Pyrk notifications',
            description: 'Walk reminders and member announcements',
            importance: 5,
            visibility: 1,
            vibration: true
          });
        } catch (error) {}
      }

      listenerHandles.push(await push.addListener('registration', async function (token) {
        try { await savePushToken(token && token.value); } catch (error) {
          console.error('Pyrk push token registration failed:', safeMessage(error));
        }
      }));
      listenerHandles.push(await push.addListener('registrationError', function (error) {
        console.error('Pyrk native push registration error:', safeMessage(error));
        document.dispatchEvent(new CustomEvent('pyrk:native-push-error', {detail: {message: safeMessage(error)}}));
      }));
      listenerHandles.push(await push.addListener('pushNotificationReceived', function (notification) {
        document.dispatchEvent(new CustomEvent('pyrk:native-push-received', {detail: notification || {}}));
      }));
      listenerHandles.push(await push.addListener('pushNotificationActionPerformed', function (action) {
        const notification = action && action.notification ? action.notification : {};
        const data = notification.data || {};
        const target = sameOriginPath(data.url || data.path || data.link || '');
        if (target) location.href = target;
      }));

      let permission = await push.checkPermissions();
      if (permission && (permission.receive === 'prompt' || permission.receive === 'prompt-with-rationale')) {
        permission = await push.requestPermissions();
      }
      if (!permissionGranted(permission && permission.receive)) {
        pushSetupStarted = false;
        return {started: false, reason: 'denied'};
      }
      await push.register();
      return {started: true, token: lastPushToken};
    } catch (error) {
      pushSetupStarted = false;
      throw new Error(safeMessage(error, 'Could not enable push notifications.'));
    }
  }

  async function unregisterPushNotifications() {
    const push = plugin('PushNotifications');
    let storedToken = lastPushToken;
    try { storedToken = storedToken || localStorage.getItem('pyrk_push_token_registered') || ''; } catch (error) {}
    try {
      if (storedToken && window.PYRK_CSRF) await postDeviceToken({action: 'unregister', token: storedToken});
    } finally {
      if (push && typeof push.unregister === 'function') await push.unregister();
      lastPushToken = '';
      try { localStorage.removeItem('pyrk_push_token_registered'); } catch (error) {}
    }
  }

  async function installAppLifecycleListeners() {
    const app = plugin('App');
    if (!app || typeof app.addListener !== 'function') return;
    try {
      listenerHandles.push(await app.addListener('appRestoredResult', async function (event) {
        if (!event || !event.success || String(event.pluginId || '') !== 'Camera' || !event.data) return;
        try {
          const file = await mediaResultToFile(event.data, 'pyrk-restored-camera');
          document.dispatchEvent(new CustomEvent('pyrk:native-camera-restored', {
            detail: {file, result: event.data, methodName: event.methodName || ''}
          }));
        } catch (error) {
          document.dispatchEvent(new CustomEvent('pyrk:native-camera-error', {
            detail: {message: safeMessage(error, 'Could not restore the captured photo.')}
          }));
        }
      }));
    } catch (error) {
      // Lifecycle restoration is protective; failure must not block normal camera use.
    }
  }

  function installNativeLogoutGuard() {
    document.querySelectorAll('a[href*="logout=1"], .btn-logout').forEach(function (link) {
      if (!link || link.dataset.nativeLogoutGuard === '1') return;
      link.dataset.nativeLogoutGuard = '1';
      link.addEventListener('click', async function (event) {
        if (!isNativeApp() || link.dataset.nativeLogoutRunning === '1') return;
        event.preventDefault();
        link.dataset.nativeLogoutRunning = '1';
        const target = link.href || link.getAttribute('href') || 'login-dev.php?logout=1';
        try {
          await Promise.race([
            unregisterPushNotifications(),
            new Promise(resolve => setTimeout(resolve, 1800))
          ]);
        } catch (error) {
          // Logout must continue even when the device is offline.
        }
        location.href = target;
      });
    });
  }

  async function cleanup() {
    for (const handle of listenerHandles.splice(0)) {
      try { if (handle && typeof handle.remove === 'function') await handle.remove(); } catch (error) {}
    }
    for (const id of Array.from(nativeWatchIds.keys())) {
      try { await clearWatch(id); } catch (error) {}
    }
  }

  const api = {
    isNativeApp,
    getPlatform,
    isAvailable,
    capturePhoto,
    getCurrentPosition,
    watchPosition,
    clearWatch,
    scanQr,
    setupPushNotifications,
    unregisterPushNotifications,
    cleanup
  };
  window.PyrkNativeDevice = api;

  if (isNativeApp()) {
    try {
      sessionStorage.setItem('pyrk_native_app', '1');
      localStorage.setItem('pyrk_native_app', '1');
    } catch (error) {}

    const start = function () {
      installAppLifecycleListeners();
      installNativeLogoutGuard();
      // Only authenticated pages expose a CSRF token. Login/activation pages stay untouched.
      if (window.PYRK_CSRF && isAvailable('push')) {
        setupPushNotifications({automatic: true}).catch(function (error) {
          console.warn('Pyrk push setup:', safeMessage(error));
        });
      }
    };
    if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', start, {once: true});
    else start();
  }
})();
