import type { ConsoleLevel } from './types'

// export const VIRTUAL_MODULE_ID = 'virtual:tanstack-devtools-console'
// export const RESOLVED_VIRTUAL_MODULE_ID = '\0' + VIRTUAL_MODULE_ID

/**
 * Generates inline code to inject into entry files (both client and server).
 * This code detects the environment at runtime and:
 *
 * CLIENT:
 * 1. Store original console methods
 * 2. Create batched wrappers that POST to server via fetch
 * 3. Override global console with the wrapped methods
 * 4. Listen for server console logs via SSE
 *
 * SERVER (Nitro/Vinxi runtime):
 * 1. Store original console methods
 * 2. Create batched wrappers that POST to Vite dev server
 * 3. Override global console - original logging still happens, just also pipes to Vite
 *
 * Returns the inline code as a string - no imports needed since we use fetch.
 */
export function generateConsolePipeCode(
  levels: Array<ConsoleLevel>,
  viteServerUrl: string,
): string {
  const levelsArray = JSON.stringify(levels)

  return `
;(function __tsdConsolePipe() {
  // Detect environment
  var isServer = typeof window === 'undefined';
  var envKey = isServer ? '__TSD_SERVER_CONSOLE_PIPE_INITIALIZED__' : '__TSD_CONSOLE_PIPE_INITIALIZED__';
  var globalObj = isServer ? globalThis : window;
  
  // Only run once per environment
  if (globalObj[envKey]) return;
  globalObj[envKey] = true;

  var CONSOLE_LEVELS = ${levelsArray};
  var VITE_SERVER_URL = ${JSON.stringify(viteServerUrl)};

  // Store original console methods before we override them
  var originalConsole = {};
  for (var i = 0; i < CONSOLE_LEVELS.length; i++) {
    var level = CONSOLE_LEVELS[i];
    originalConsole[level] = console[level].bind(console);
  }

  // Simple inline batcher implementation
  var batchedEntries = [];
  var batchTimeout = null;
  var BATCH_WAIT = isServer ? 50 : 100;
  var BATCH_MAX_SIZE = isServer ? 20 : 50;

  function flushBatch() {
    if (batchedEntries.length === 0) return;
    
    var entries = batchedEntries;
    batchedEntries = [];
    batchTimeout = null;
    
    // Determine endpoint based on environment
    var endpoint = isServer 
      ? VITE_SERVER_URL + '/__tsd/console-pipe/server'
      : '/__tsd/console-pipe';
    
    // Send to Vite server via fetch
    fetch(endpoint, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ entries: entries }),
    }).catch(function( ) {
     // Swallow errors  
    });
  }

  function addToBatch(entry) {
    batchedEntries.push(entry);
    
    if (batchedEntries.length >= BATCH_MAX_SIZE) {
      if (batchTimeout) {
        clearTimeout(batchTimeout);
        batchTimeout = null;
      }
      flushBatch();
    } else if (!batchTimeout) {
      batchTimeout = setTimeout(flushBatch, BATCH_WAIT);
    }
  }

  var MAX_DEPTH = 6;
  var MAX_ARRAY_LEN = 100;
  var MAX_OBJECT_KEYS = 100;
  var MAX_STRING_LENGTH = 10000;

  function truncateString(value) {
    if (value.length <= MAX_STRING_LENGTH) return value;
    return value.slice(0, MAX_STRING_LENGTH) + '... (' + (value.length - MAX_STRING_LENGTH) + ' more chars)';
  }

  function formatDomElement(element) {
    var tagName = element.tagName ? element.tagName.toLowerCase() : 'element';
    var output = '<' + tagName;
    var attrs = ['id', 'class', 'name', 'type', 'role', 'aria-label', 'data-testid'];

    for (var attrIndex = 0; attrIndex < attrs.length; attrIndex++) {
      var attrName = attrs[attrIndex];
      var attrValue = element.getAttribute && element.getAttribute(attrName);
      if (attrValue) {
        output += ' ' + attrName + '="' + truncateString(String(attrValue)).replace(/"/g, '&quot;') + '"';
      }
    }

    return output + '>';
  }

  function getObjectTypeName(arg) {
    return arg && arg.constructor && arg.constructor.name ? arg.constructor.name : 'Object';
  }

  function formatArrayBufferView(arg) {
    var length = typeof arg.length === 'number' ? arg.length : arg.byteLength;
    return '[' + getObjectTypeName(arg) + '(' + length + ')]';
  }

  function serializeConsoleArg(arg, seen, depth) {
    if (depth === undefined) depth = 0;
    if (arg === undefined) return 'undefined';
    if (arg === null) return null;

    var argType = typeof arg;

    if (argType === 'function') return '[Function' + (arg.name ? ': ' + arg.name : '') + ']';
    if (argType === 'symbol') return arg.toString();
    if (argType === 'bigint') return arg.toString() + 'n';
    if (argType === 'string') return truncateString(arg);
    if (argType !== 'object') return arg;

    if (!isServer) {
      if (
        arg.nodeType === 1 &&
        typeof arg.tagName === 'string' &&
        typeof arg.getAttribute === 'function'
      ) {
        return formatDomElement(arg);
      }
      if (arg.nodeType === 9) {
        return '[Document]';
      }
      if (typeof Window !== 'undefined' && arg instanceof Window) {
        return '[Window]';
      }
      if (typeof Event !== 'undefined' && arg instanceof Event) {
        return {
          type: arg.type,
          target: serializeConsoleArg(arg.target, seen, depth + 1),
          currentTarget: serializeConsoleArg(arg.currentTarget, seen, depth + 1),
          defaultPrevented: arg.defaultPrevented,
        };
      }
      if (typeof Node !== 'undefined' && arg instanceof Node) {
        return '[Node: ' + arg.nodeName + ']';
      }
    }

    if (arg instanceof Error) {
      return {
        name: arg.name,
        message: truncateString(arg.message),
        stack: arg.stack ? truncateString(arg.stack) : arg.stack,
      };
    }

    if (arg instanceof Date) {
      return arg.toISOString();
    }

    if (arg instanceof RegExp) {
      return arg.toString();
    }

    if (typeof ArrayBuffer !== 'undefined') {
      if (ArrayBuffer.isView && ArrayBuffer.isView(arg)) {
        return formatArrayBufferView(arg);
      }
      if (arg instanceof ArrayBuffer) {
        return '[ArrayBuffer(' + arg.byteLength + ')]';
      }
    }

    if (typeof SharedArrayBuffer !== 'undefined' && arg instanceof SharedArrayBuffer) {
      return '[SharedArrayBuffer(' + arg.byteLength + ')]';
    }

    if (depth >= MAX_DEPTH) {
      return '[MaxDepth]';
    }

    if (seen.indexOf(arg) !== -1) {
      return '[Circular]';
    }

    seen.push(arg);

    if (Array.isArray(arg)) {
      var arrayResult = [];
      var arrayLength = Math.min(arg.length, MAX_ARRAY_LEN);
      for (var arrayIndex = 0; arrayIndex < arrayLength; arrayIndex++) {
        arrayResult.push(serializeConsoleArg(arg[arrayIndex], seen, depth + 1));
      }
      if (arg.length > MAX_ARRAY_LEN) {
        arrayResult.push('... (' + (arg.length - MAX_ARRAY_LEN) + ' more)');
      }
      seen.pop();
      return arrayResult;
    }

    var objectResult = {};
    var keys = Object.keys(arg);
    var keyLength = Math.min(keys.length, MAX_OBJECT_KEYS);
    for (var keyIndex = 0; keyIndex < keyLength; keyIndex++) {
      var key = keys[keyIndex];
      try {
        objectResult[key] = serializeConsoleArg(arg[key], seen, depth + 1);
      } catch (error) {
        objectResult[key] = '[Thrown: ' + String(error) + ']';
      }
    }
    if (keys.length > MAX_OBJECT_KEYS) {
      objectResult['...'] = '(' + (keys.length - MAX_OBJECT_KEYS) + ' more keys)';
    }
    seen.pop();
    return objectResult;
  }

  // Override global console methods
  for (var j = 0; j < CONSOLE_LEVELS.length; j++) {
    (function(level) {
      var original = originalConsole[level];
      console[level] = function() {
        var args = Array.prototype.slice.call(arguments);
        
        // Always call original first so logs appear normally
        original.apply(console, args);
        
        // Skip our own TSD Console Pipe logs to avoid recursion/noise
        if (args.length > 0 && typeof args[0] === 'string' && 
            (args[0].indexOf('[TSD Console Pipe]') !== -1 || 
             args[0].indexOf('[@tanstack/devtools') !== -1)) {
          return;
        }

        var safeArgs = args.map(function(arg) {
          try {
            return serializeConsoleArg(arg, [], 0);
          } catch (e) {
            return String(arg);
          }
        });

        var entry = {
          level: level,
          args: safeArgs,
          source: isServer ? 'server' : 'client',
          timestamp: Date.now(),
        };
        
        addToBatch(entry);
      };
    })(CONSOLE_LEVELS[j]);
  }

  // CLIENT ONLY: Listen for server console logs via SSE
  if (!isServer) {
    // Transform server log args - strip ANSI codes and convert source paths to clickable URLs
    function transformServerLogArgs(args) {
      var escChar = String.fromCharCode(27);
      var transformed = [];
      
      for (var k = 0; k < args.length; k++) {
        var arg = args[k];
        if (typeof arg === 'string') {
          // Strip ANSI escape sequences (ESC[...m patterns)
          var cleaned = arg;
          // Remove ESC character followed by [...m] - need to build regex dynamically
          while (cleaned.indexOf(escChar) !== -1) {
            cleaned = cleaned.split(escChar).join('');
          }
          // Also remove any leftover bracket codes like [35m
          cleaned = cleaned.replace(/\\[[0-9;]*m/g, '');
          
          // Transform source paths to clickable URLs
          // Match patterns like /src/components/Header.tsx:17:3
          var sourceRegex = /(\\/[^\\s]+:\\d+:\\d+)/g;
          cleaned = cleaned.replace(sourceRegex, function(match) {
            return window.location.origin + '/__tsd/open-source?source=' + encodeURIComponent(match);
          });
          
          if (cleaned.trim()) {
            transformed.push(cleaned);
          }
        } else {
          transformed.push(arg);
        }
      }
      
      return transformed;
    }

    var eventSource = new EventSource('/__tsd/console-pipe/sse');
    
    eventSource.onmessage = function(event) {
      try {
        var data = JSON.parse(event.data);
        if (data.entries) {
          for (var m = 0; m < data.entries.length; m++) {
            var entry = data.entries[m];
            var transformedArgs = transformServerLogArgs(entry.args);
            var prefix = '%c[Server]%c';
            var prefixStyle = 'color: #9333ea; font-weight: bold;';
            var resetStyle = 'color: inherit;';
            var logMethod = originalConsole[entry.level] || originalConsole.log;
            logMethod.apply(console, [prefix, prefixStyle, resetStyle].concat(transformedArgs));
          }
        }
      } catch (err) {
        // Swallow errors  
      }
    };

    eventSource.onerror = function() {
      // Swallow errors  
    };

    // Flush on page unload
    window.addEventListener('beforeunload', flushBatch);
  }
})();
`
}
