import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

/**
 * Utility for merging Tailwind CSS classes intelligently
 * Combines clsx (conditional classes) with tailwind-merge (conflict resolution)
 */
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

/**
 * Input Sanitization Utilities
 * Centralized helpers for cleaning user input values
 */

export type SanitizeOptions = {
  /** Remove all whitespace characters (default: true) */
  removeSpaces?: boolean;
  /** Remove leading/trailing whitespace only (default: false) */
  trim?: boolean;
  /** Convert to lowercase (default: false) */
  lowercase?: boolean;
  /** Remove specific characters via regex (default: null) */
  removePattern?: RegExp;
}

/**
 * Sanitize input value for design properties
 * Default: Removes all spaces to prevent invalid Tailwind classes
 *
 * @param value - The input value to sanitize
 * @param options - Optional sanitization configuration
 * @returns Sanitized value
 *
 * @example
 * sanitizeInput('10 rem') // '10rem'
 * sanitizeInput('  100px  ', { trim: true, removeSpaces: false }) // '100px'
 * sanitizeInput('#FF 00 00') // '#FF0000'
 */
export function sanitizeInput(value: string, options: SanitizeOptions = {}): string {
  const {
    removeSpaces = true,
    trim = false,
    lowercase = false,
    removePattern = null
  } = options;

  let sanitized = value;

  // Remove spaces (default behavior for Tailwind classes)
  if (removeSpaces) {
    sanitized = sanitized.replace(/\s+/g, '');
  }

  // Trim only (alternative to removing all spaces)
  if (trim && !removeSpaces) {
    sanitized = sanitized.trim();
  }

  // Convert to lowercase
  if (lowercase) {
    sanitized = sanitized.toLowerCase();
  }

  // Remove custom pattern
  if (removePattern) {
    sanitized = sanitized.replace(removePattern, '');
  }

  return sanitized;
}

/**
 * Quick helper: Remove all spaces from value
 * Common use case for Tailwind class values
 *
 * @param value - The input value
 * @returns Value without any spaces
 */
export function removeSpaces(value: string): string {
  return sanitizeInput(value, { removeSpaces: true });
}

/**
 * Date Formatting Utilities
 */

/**
 * Format a date string or Date object using a custom format string
 *
 * Supported tokens:
 * - YYYY: 4-digit year (2025)
 * - YY: 2-digit year (25)
 * - MMMM: Full month name (November)
 * - MMM: Short month name (Nov)
 * - MM: 2-digit month (11)
 * - M: Month (11)
 * - DD: 2-digit day (12)
 * - D: Day (12)
 * - dddd: Full day name (Wednesday)
 * - ddd: Short day name (Wed)
 * - HH: 2-digit hour 24h (09)
 * - H: Hour 24h (9)
 * - hh: 2-digit hour 12h (09)
 * - h: Hour 12h (9)
 * - mm: 2-digit minutes (38)
 * - m: Minutes (38)
 * - ss: 2-digit seconds (05)
 * - s: Seconds (5)
 * - A: AM/PM
 * - a: am/pm
 *
 * @param date - Date string (ISO format) or Date object
 * @param format - Format string using tokens (default: 'MMM D YYYY, HH:mm')
 * @returns Formatted date string
 *
 * @example
 * formatDate('2025-11-12T09:38:45.000Z', 'MMM D YYYY, HH:mm') // 'Nov 12 2025, 09:38'
 * formatDate(new Date(), 'YYYY-MM-DD') // '2025-11-12'
 * formatDate(date, 'dddd, MMMM D, YYYY') // 'Wednesday, November 12, 2025'
 * formatDate(date, 'MM/DD/YYYY hh:mm A') // '11/12/2025 09:38 AM'
 */
export function formatDate(date: string | Date | null | undefined, format: string = 'MMM D YYYY, HH:mm'): string {
  if (!date) return '-';

  const dateObj = typeof date === 'string' ? new Date(date) : date;

  // Check if date is valid
  if (isNaN(dateObj.getTime())) return '-';

  const months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
  const monthsFull = ['January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December'];
  const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'];
  const daysFull = ['Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday'];

  const year = dateObj.getFullYear();
  const month = dateObj.getMonth();
  const day = dateObj.getDate();
  const hours = dateObj.getHours();
  const minutes = dateObj.getMinutes();
  const seconds = dateObj.getSeconds();
  const dayOfWeek = dateObj.getDay();

  // Helper to pad numbers
  const pad = (num: number) => String(num).padStart(2, '0');

  // Convert to 12-hour format
  const hours12 = hours % 12 || 12;
  const isPM = hours >= 12;

  // Token replacements
  const tokens: Record<string, string> = {
    'YYYY': String(year),
    'YY': String(year).slice(-2),
    'MMMM': monthsFull[month],
    'MMM': months[month],
    'MM': pad(month + 1),
    'M': String(month + 1),
    'DD': pad(day),
    'D': String(day),
    'dddd': daysFull[dayOfWeek],
    'ddd': days[dayOfWeek],
    'HH': pad(hours),
    'H': String(hours),
    'hh': pad(hours12),
    'h': String(hours12),
    'mm': pad(minutes),
    'm': String(minutes),
    'ss': pad(seconds),
    's': String(seconds),
    'A': isPM ? 'PM' : 'AM',
    'a': isPM ? 'pm' : 'am',
  };

  // Replace tokens in format string using placeholders to avoid double-replacement
  // (e.g., 'a' in 'Jan' being replaced by AM/PM token)
  let result = format;
  const placeholders: string[] = [];

  // Sort by length (longest first) to avoid partial replacements
  const sortedTokens = Object.keys(tokens).sort((a, b) => b.length - a.length);

  // First pass: replace tokens with unique placeholders
  for (let i = 0; i < sortedTokens.length; i++) {
    const token = sortedTokens[i];
    const placeholder = `\x00${i}\x00`; // Use null bytes as unique markers
    if (result.includes(token)) {
      result = result.split(token).join(placeholder);
      placeholders[i] = tokens[token];
    }
  }

  // Second pass: replace placeholders with actual values
  for (let i = 0; i < sortedTokens.length; i++) {
    if (placeholders[i] !== undefined) {
      const placeholder = `\x00${i}\x00`;
      result = result.split(placeholder).join(placeholders[i]);
    }
  }

  return result;
}

/**
 * Format a relative time string (e.g., "2 hours ago", "just now")
 *
 * @param date - Date string (ISO format) or Date object
 * @returns Relative time string
 *
 * @example
 * formatRelativeTime(new Date()) // 'just now'
 * formatRelativeTime('2025-11-11T09:38:45.000Z') // '2 hours ago'
 */
export function formatRelativeTime(date: string | Date | null | undefined, showSeconds: boolean = true): string {
  if (!date) return '-';

  const dateObj = typeof date === 'string' ? new Date(date) : date;

  if (isNaN(dateObj.getTime())) return '-';

  const now = new Date();
  const diffMs = now.getTime() - dateObj.getTime();
  const diffSecs = Math.floor(diffMs / 1000);
  const diffMins = Math.floor(diffSecs / 60);
  const diffHours = Math.floor(diffMins / 60);
  const diffDays = Math.floor(diffHours / 24);
  const diffMonths = Math.floor(diffDays / 30);
  const diffYears = Math.floor(diffDays / 365);

  if (diffSecs < 10) return 'just now';
  if (diffSecs < 60) return showSeconds ? `${diffSecs} seconds ago` : 'a few seconds ago';
  if (diffMins === 1) return '1 minute ago';
  if (diffMins < 60) return `${diffMins} minutes ago`;
  if (diffHours === 1) return '1 hour ago';
  if (diffHours < 24) return `${diffHours} hours ago`;
  if (diffDays === 1) return 'yesterday';
  if (diffDays < 30) return `${diffDays} days ago`;
  if (diffMonths === 1) return '1 month ago';
  if (diffMonths < 12) return `${diffMonths} months ago`;
  if (diffYears === 1) return '1 year ago';
  return `${diffYears} years ago`;
}

/**
 * Validation Utilities
 */

/**
 * Validate if a string is a valid UUID format
 *
 * @param str - The string to validate
 * @returns True if the string matches UUID format (e.g., "550e8400-e29b-41d4-a716-446655440000")
 *
 * @example
 * isValidUUID('550e8400-e29b-41d4-a716-446655440000') // true
 * isValidUUID('ghi') // false
 * isValidUUID('not-a-uuid') // false
 */
export function isValidUUID(str: string): boolean {
  const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
  return uuidRegex.test(str);
}

/**
 * Generate a unique ID with optional prefix
 * @param prefix - Optional 3 letter prefix to prepend (e.g., 'lyr' for 'layer')
 * @returns Unique ID string (e.g., "lyr-mip1xm2qt9vvh")
 */
export function generateId(prefix?: string): string {
  const timestamp = Date.now().toString(36);
  const random = Math.floor(Math.random() * 36 ** 6).toString(36);
  const id = `${timestamp}${random}`;
  return prefix ? `${prefix}-${id}` : id;
}

/**
 * Deep clone object
 * @param obj - The object to clone
 * @returns Deep cloned copy of the object
 */
export function cloneDeep<T>(obj: T): T {
  if (obj === null || typeof obj !== 'object') return obj;
  if (obj instanceof Date) return new Date(obj.getTime()) as T;
  if (obj instanceof Array) return obj.map(item => cloneDeep(item)) as T;
  if (obj instanceof Object) {
    const clonedObj = {} as Record<string, unknown>;
    for (const key in obj) {
      if (Object.prototype.hasOwnProperty.call(obj, key)) {
        clonedObj[key] = cloneDeep((obj as Record<string, unknown>)[key]);
      }
    }
    return clonedObj as T;
  }
  throw new Error('Unable to clone object');
}

/**
 * Checks if the app is running as a cloud-hosted version
 * Returns false for self-hosted/opensource installations
 */
export function isCloudVersion(): boolean {
  return process.env.NEXT_PUBLIC_IS_CLOUD === 'true';
}

/**
 * Returns the browser's IANA timezone (e.g. "Europe/London")
 */
export function getDetectedTimezone(): string {
  if (typeof Intl === 'undefined' || !Intl.DateTimeFormat) {
    return '';
  }
  return Intl.DateTimeFormat().resolvedOptions().timeZone || '';
}
