// Service Worker Generator
// Generates customized service worker scripts based on configuration

export interface ServiceWorkerConfig {
  cacheName?: string;
  version?: string;
  staticAssets?: string[];
  apiEndpoints?: string[];
  cacheStrategy?: 'cacheFirst' | 'networkFirst' | 'staleWhileRevalidate';
  maxCacheAge?: number; // in milliseconds
  maxCacheSize?: number; // number of items
  enableBackgroundSync?: boolean;
  enablePushNotifications?: boolean;
  customStrategies?: {
    [pattern: string]: 'cacheFirst' | 'networkFirst' | 'staleWhileRevalidate';
  };
}

export class ServiceWorkerGenerator {
  private config: Required<ServiceWorkerConfig>;

  constructor(config: ServiceWorkerConfig = {}) {
    this.config = {
      cacheName: config.cacheName || 'app-cache',
      version: config.version || 'v1',
      staticAssets: config.staticAssets || ['/', '/index.html'],
      apiEndpoints: config.apiEndpoints || ['/api/'],
      cacheStrategy: config.cacheStrategy || 'staleWhileRevalidate',
      maxCacheAge: config.maxCacheAge || 7 * 24 * 60 * 60 * 1000, // 7 days
      maxCacheSize: config.maxCacheSize || 100,
      enableBackgroundSync: config.enableBackgroundSync || false,
      enablePushNotifications: config.enablePushNotifications || false,
      customStrategies: config.customStrategies || {},
    };
  }

  generateServiceWorker(): string {
    return `
// Generated Service Worker
// Configuration: ${JSON.stringify(this.config, null, 2)}

const CACHE_NAME = '${this.config.cacheName}-${this.config.version}';
const STATIC_CACHE_NAME = '${this.config.cacheName}-static-${this.config.version}';
const DYNAMIC_CACHE_NAME = '${this.config.cacheName}-dynamic-${this.config.version}';
const MAX_CACHE_AGE = ${this.config.maxCacheAge};
const MAX_CACHE_SIZE = ${this.config.maxCacheSize};

// Assets to cache immediately on install
const STATIC_ASSETS = ${JSON.stringify(this.config.staticAssets, null, 2)};

// API endpoints configuration
const API_ENDPOINTS = ${JSON.stringify(this.config.apiEndpoints, null, 2)};

// Custom strategies configuration
const CUSTOM_STRATEGIES = ${JSON.stringify(this.config.customStrategies, null, 2)};

${this.generateInstallHandler()}

${this.generateActivateHandler()}

${this.generateFetchHandler()}

${this.generateCacheStrategies()}

${this.generateHelperFunctions()}

${this.generateMessageHandler()}

${this.config.enableBackgroundSync ? this.generateBackgroundSync() : ''}

${this.config.enablePushNotifications ? this.generatePushNotifications() : ''}

console.log('[SW] Service worker generated and loaded');
`;
  }

  private generateInstallHandler(): string {
    return `
// Install event - cache static assets
self.addEventListener('install', (event) => {
  console.log('[SW] Installing service worker...');
  
  event.waitUntil(
    Promise.all([
      caches.open(STATIC_CACHE_NAME).then((cache) => {
        console.log('[SW] Caching static assets');
        return cache.addAll(STATIC_ASSETS);
      }),
      self.skipWaiting()
    ])
  );
});`;
  }

  private generateActivateHandler(): string {
    return `
// Activate event - clean up old caches
self.addEventListener('activate', (event) => {
  console.log('[SW] Activating service worker...');
  
  event.waitUntil(
    Promise.all([
      caches.keys().then((cacheNames) => {
        return Promise.all(
          cacheNames.map((cacheName) => {
            if (!cacheName.includes('${this.config.cacheName}-${this.config.version}')) {
              console.log('[SW] Deleting old cache:', cacheName);
              return caches.delete(cacheName);
            }
          })
        );
      }),
      self.clients.claim()
    ])
  );
});`;
  }

  private generateFetchHandler(): string {
    return `
// Fetch event - serve from cache with configured strategy
self.addEventListener('fetch', (event) => {
  const request = event.request;
  const url = new URL(request.url);
  
  if (request.method !== 'GET') return;
  if (!url.protocol.startsWith('http')) return;
  
  // Check for custom strategies first
  for (const [pattern, strategy] of Object.entries(CUSTOM_STRATEGIES)) {
    if (url.pathname.includes(pattern)) {
      event.respondWith(executeStrategy(request, strategy));
      return;
    }
  }
  
  // Default strategies
  if (isStaticAsset(url)) {
    event.respondWith(executeStrategy(request, 'cacheFirst'));
  } else if (isAPIRequest(url)) {
    event.respondWith(executeStrategy(request, 'networkFirst'));
  } else {
    event.respondWith(executeStrategy(request, '${this.config.cacheStrategy}'));
  }
});`;
  }

  private generateCacheStrategies(): string {
    return `
// Cache strategies
async function executeStrategy(request, strategy) {
  switch (strategy) {
    case 'cacheFirst':
      return cacheFirst(request);
    case 'networkFirst':
      return networkFirst(request);
    case 'staleWhileRevalidate':
      return staleWhileRevalidate(request);
    default:
      return fetch(request);
  }
}

async function cacheFirst(request) {
  try {
    const cache = await caches.open(STATIC_CACHE_NAME);
    const cachedResponse = await cache.match(request);
    
    if (cachedResponse && !isExpired(cachedResponse)) {
      return cachedResponse;
    }
    
    const networkResponse = await fetch(request);
    if (networkResponse.ok) {
      await cache.put(request, networkResponse.clone());
      await limitCacheSize(cache);
    }
    return networkResponse;
  } catch (error) {
    const cache = await caches.open(STATIC_CACHE_NAME);
    const fallback = await cache.match(request);
    if (fallback) return fallback;
    
    if (request.destination === 'document') {
      return cache.match('/index.html') || new Response('Offline', { status: 503 });
    }
    throw error;
  }
}

async function networkFirst(request) {
  try {
    const networkResponse = await fetch(request);
    if (networkResponse.ok) {
      const cache = await caches.open(DYNAMIC_CACHE_NAME);
      await cache.put(request, networkResponse.clone());
      await limitCacheSize(cache);
    }
    return networkResponse;
  } catch (error) {
    const cache = await caches.open(DYNAMIC_CACHE_NAME);
    const cachedResponse = await cache.match(request);
    if (cachedResponse && !isExpired(cachedResponse)) {
      return cachedResponse;
    }
    throw error;
  }
}

async function staleWhileRevalidate(request) {
  const cache = await caches.open(DYNAMIC_CACHE_NAME);
  const cachedResponse = await cache.match(request);
  
  const fetchPromise = fetch(request).then(async (networkResponse) => {
    if (networkResponse.ok) {
      await cache.put(request, networkResponse.clone());
      await limitCacheSize(cache);
    }
    return networkResponse;
  }).catch(() => null);
  
  if (cachedResponse && !isExpired(cachedResponse)) {
    return cachedResponse;
  }
  
  return fetchPromise || cachedResponse || new Response('Offline', { status: 503 });
}`;
  }

  private generateHelperFunctions(): string {
    return `
// Helper functions
function isStaticAsset(url) {
  const staticExtensions = ['.js', '.css', '.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.woff', '.woff2', '.ttf'];
  const pathname = url.pathname.toLowerCase();
  
  return staticExtensions.some(ext => pathname.endsWith(ext)) ||
         pathname.includes('/assets/') ||
         pathname.includes('/static/') ||
         STATIC_ASSETS.includes(pathname);
}

function isAPIRequest(url) {
  return API_ENDPOINTS.some(endpoint => url.pathname.startsWith(endpoint)) ||
         url.hostname !== location.hostname;
}

function isExpired(response) {
  const dateHeader = response.headers.get('date');
  if (!dateHeader) return false;
  
  const responseDate = new Date(dateHeader);
  const now = new Date();
  return (now.getTime() - responseDate.getTime()) > MAX_CACHE_AGE;
}

async function limitCacheSize(cache) {
  const requests = await cache.keys();
  if (requests.length > MAX_CACHE_SIZE) {
    // Remove oldest entries
    const sortedRequests = requests.sort((a, b) => {
      const aDate = new Date(a.headers.get('date') || 0);
      const bDate = new Date(b.headers.get('date') || 0);
      return aDate - bDate;
    });
    
    const toDelete = sortedRequests.slice(0, requests.length - MAX_CACHE_SIZE);
    await Promise.all(toDelete.map(request => cache.delete(request)));
  }
}

async function getCacheSize() {
  const cacheNames = await caches.keys();
  let totalSize = 0;
  
  for (const cacheName of cacheNames) {
    const cache = await caches.open(cacheName);
    const requests = await cache.keys();
    totalSize += requests.length;
  }
  
  return totalSize;
}

async function clearAllCaches() {
  const cacheNames = await caches.keys();
  await Promise.all(cacheNames.map(name => caches.delete(name)));
}`;
  }

  private generateMessageHandler(): string {
    return `
// Message handling
self.addEventListener('message', (event) => {
  const { type, data } = event.data;
  
  switch (type) {
    case 'SKIP_WAITING':
      self.skipWaiting();
      break;
      
    case 'GET_CACHE_SIZE':
      getCacheSize().then(size => {
        event.ports[0].postMessage({ type: 'CACHE_SIZE', size });
      });
      break;
      
    case 'CLEAR_CACHE':
      clearAllCaches().then(() => {
        event.ports[0].postMessage({ type: 'CACHE_CLEARED' });
      });
      break;
      
    case 'CACHE_URLS':
      if (data && data.urls) {
        cacheUrls(data.urls).then(() => {
          event.ports[0].postMessage({ type: 'URLS_CACHED' });
        });
      }
      break;
  }
});

async function cacheUrls(urls) {
  const cache = await caches.open(DYNAMIC_CACHE_NAME);
  
  for (const url of urls) {
    try {
      const response = await fetch(url);
      if (response.ok) {
        await cache.put(url, response);
      }
    } catch (error) {
      console.error('[SW] Failed to cache:', url, error);
    }
  }
}`;
  }

  private generateBackgroundSync(): string {
    return `
// Background Sync
self.addEventListener('sync', (event) => {
  if (event.tag === 'background-sync') {
    event.waitUntil(doBackgroundSync());
  }
});

async function doBackgroundSync() {
  // Implement your background sync logic here
  console.log('[SW] Background sync triggered');
}`;
  }

  private generatePushNotifications(): string {
    return `
// Push Notifications
self.addEventListener('push', (event) => {
  const options = {
    body: event.data ? event.data.text() : 'New notification',
    icon: '/icon-192x192.png',
    badge: '/badge-72x72.png',
    vibrate: [200, 100, 200],
    data: {
      dateOfArrival: Date.now(),
      primaryKey: 1
    },
    actions: [
      {
        action: 'explore',
        title: 'Explore',
        icon: '/explore-icon.png'
      },
      {
        action: 'close',
        title: 'Close',
        icon: '/close-icon.png'
      }
    ]
  };

  event.waitUntil(
    self.registration.showNotification('Push Notification', options)
  );
});

self.addEventListener('notificationclick', (event) => {
  event.notification.close();

  if (event.action === 'explore') {
    event.waitUntil(
      clients.openWindow('/')
    );
  }
});`;
  }

  // Method to write the generated service worker to a file
  writeToFile(): void {
    const serviceWorkerCode = this.generateServiceWorker();
    
    // In a real implementation, you would write to the file system
    // For now, we'll just log it or return it
    console.log('Generated Service Worker:');
    console.log(serviceWorkerCode);
    
    // You can use this with Node.js fs module:
    // import { writeFileSync } from 'fs';
    // writeFileSync(filePath, serviceWorkerCode, 'utf8');
  }
}

// Export a convenience function to generate a service worker with common settings
export function generateServiceWorkerForApp(config: ServiceWorkerConfig = {}): string {
  const generator = new ServiceWorkerGenerator({
    cacheName: 'cpw-spoof',
    version: 'v1',
    staticAssets: [
      '/',
      '/index.html',
      '/src/main.tsx',
      '/src/App.tsx',
      '/src/index.css',
      '/src/App.css',
      '/assets/react.svg',
      '/poppulo-logo.svg',
    ],
    apiEndpoints: ['/api/', '/graphql'],
    cacheStrategy: 'staleWhileRevalidate',
    maxCacheAge: 7 * 24 * 60 * 60 * 1000, // 7 days
    maxCacheSize: 50,
    ...config,
  });

  return generator.generateServiceWorker();
}

export default ServiceWorkerGenerator;
