const fs = require('fs');
const path = require('path');
const ConfigReader = require('../config/config-reader');

/**
 * Validator untuk payload files - fokus HANYA pada validasi
 */
class PayloadValidator {

  /**
   * Daftar properti yang required dalam payload
   */
  static get requiredProperties() {
    return {
      tableName: {
        type: 'string',
        description: 'Nama tabel database',
        example: 'users'
      },
      fieldName: {
        type: 'array',
        description: 'Array nama field/kolom',
        example: ['id', 'name', 'email', 'created_at']
      }
    };
  }

  /**
   * Daftar properti yang optional dalam payload
   */
  static get optionalProperties() {
    return {
      datatablesQuery: {
        type: 'string',
        description: 'SQL query untuk datatables (required jika action.datatables = true)',
        example: 'SELECT * FROM table_name a'
      },
      datatablesWhere: {
        type: 'array',
        description: 'Array kolom untuk where clause datatables',
        example: ['name', 'email', 'all']
      },
      searchColumns: {
        type: 'array',
        description: 'Definisi kolom yang dapat dicari',
        example: [{ name: 'name', searchable: true }, { name: 'email', searchable: true }]
      },
      filters: {
        type: 'array',
        description: 'Definisi filter yang tersedia',
        example: [{ name: 'status', type: 'combobox', targetField: 'status' }]
      },
      action: {
        type: 'object',
        description: 'Aksi yang tersedia untuk endpoint',
        example: { datatables: true, add: true, update: true, delete: true, first: true, lookup: true, list: true, export: true, import: true }
      },
      advancedQueries: {
        type: 'object',
        description: 'Query SQL tambahan dengan referensi file',
        example: { reportQuery: 'file:report-query.sql' }
      },
      nestedQueries: {
        type: 'array',
        description: 'Query nested untuk relasi',
        example: [{ name: 'user_roles', query: 'file:user-roles.sql' }]
      },
      customQueries: {
        type: 'object',
        description: 'Custom queries untuk kebutuhan khusus',
        example: { reports: { monthly: 'file:monthly-report.sql' } }
      },
      defaultScope: {
        type: 'object',
        description: 'Default scope filter per action (lookup, read)',
        example: { lookup: { is_active: true }, read: { is_active: true } }
      },
      requestScope: {
        type: 'object',
        description: 'Dynamic per-request scope filter based on authenticated user context (e.g., JWT)',
        example: { column: 'app_id', source: 'req.user.app_id', bypassRoles: ['SUPER_ADMIN'] }
      },
      fieldPolicy: {
        type: 'object',
        description: 'Per-column write protection policy using strategies model (Layer 2). Each column declares an array of strategies (Phase 1 supports "lock" and "audit"). Drives SELECT ... FOR UPDATE row locking and audit logging on UPDATE/adjust',
        example: { salary: { strategies: ['lock', 'audit'] } }
      },
      auditColumns: {
        type: 'mixed',
        description: 'Override audit columns behavior. Set to false untuk disable injection (tabel tanpa kolom audit). Set ke object { createdAt, createdBy, updatedAt, updatedBy } untuk override nama kolom. Bila tidak diset, generator pakai default BaseModel (created_at, created_by, updated_at, updated_by)',
        example: false
      }
    };
  }

  /**
   * Mencari file payload di berbagai lokasi yang mungkin
   * @param {string} payloadFileName - Nama file payload
   * @returns {string|null} Path file yang ditemukan atau null
   */
  static findPayloadFile(payloadFileName) {
    const workingDir = ConfigReader.getWorkingDirectory();
    const isPkg = ConfigReader.isPkg;
    
    // Daftar lokasi yang mungkin untuk file payload
    const possiblePaths = [
      path.join(workingDir, 'payload', payloadFileName),
      path.join(workingDir, payloadFileName),
      path.join(process.cwd(), 'payload', payloadFileName),
      path.join(process.cwd(), payloadFileName)
    ];
    
    // Jika bukan PKG, tambahkan path relatif dari __dirname
    if (!isPkg) {
      // Dari lib/validators/ ke root/, lalu ke payload/
      const rootDir = path.resolve(__dirname, '..', '..');
      possiblePaths.unshift(path.join(rootDir, 'payload', payloadFileName));
    }
    
    // Cari file di semua path yang mungkin
    for (const testPath of possiblePaths) {
      if (fs.existsSync(testPath)) {
        return testPath;
      }
    }
    
    return null;
  }

  /**
   * Membaca dan memvalidasi file payload (HANYA validasi, tidak ada processing)
   * @param {string} payloadFileName - Nama file payload
   * @param {boolean} skipSqlValidation - Skip validasi SQL keywords (default: false)
   * @returns {Object} Raw payload object yang sudah divalidasi
   * @throws {Error} Jika file tidak ditemukan atau tidak valid
   */
  static readPayloadFile(payloadFileName, skipSqlValidation = false) {
    console.log(`Searching for payload file: ${payloadFileName}`);
    
    // Cari file payload
    const payloadPath = this.findPayloadFile(payloadFileName);
    
    if (!payloadPath) {
      const workingDir = ConfigReader.getWorkingDirectory();
      const possiblePaths = [
        path.join(workingDir, 'payload', payloadFileName),
        path.join(workingDir, payloadFileName),
        path.join(process.cwd(), 'payload', payloadFileName)
      ];
      
      console.error(`Error: Payload file ${payloadFileName} not found in the following locations:`);
      possiblePaths.forEach(p => console.error(`  - ${p}`));
      throw new Error(`Payload file ${payloadFileName} not found`);
    }
    
    console.log(`Using payload from: ${payloadPath}`);
    
    try {
      // Baca dan parse file JSON
      const payloadContent = fs.readFileSync(payloadPath, 'utf8');
      const payload = JSON.parse(payloadContent);
      
      // HANYA validasi struktur payload, TIDAK ada processing
      this.validatePayloadStructure(payload, payloadFileName, skipSqlValidation);
      
      console.log('Payload validated successfully');
      
      // Return raw payload dengan path info untuk processor
      return {
        ...payload,
        _payloadPath: payloadPath,
        _payloadFileName: payloadFileName
      };
    } catch (error) {
      if (error instanceof SyntaxError) {
        console.error(`Error: Payload file ${payloadFileName} is not a valid JSON`);
        console.error(`   Error details: ${error.message}`);
        throw new Error(`Payload file ${payloadFileName} is not a valid JSON: ${error.message}`);
      }
      
      console.error(`Error while reading or parsing payload file: ${error.message}`);
      throw error;
    }
  }

  /**
   * Validasi struktur payload
   * @param {Object} payload - Payload object
   * @param {string} fileName - Nama file untuk error message
   * @param {boolean} skipSqlValidation - Skip validasi SQL keywords (default: false)
   * @throws {Error} Jika struktur tidak valid
   */
  static validatePayloadStructure(payload, fileName, skipSqlValidation = false) {
    if (!payload || typeof payload !== 'object') {
      throw new Error(`Payload ${fileName} must be an object`);
    }

    // Validasi required properties
    for (const [propName, propConfig] of Object.entries(this.requiredProperties)) {
      if (!(propName in payload)) {
        throw new Error(`Payload ${fileName} must have property '${propName}' (${propConfig.description})`);
      }

      this.validateProperty(payload[propName], propName, propConfig, fileName);
    }

    // Validasi optional properties jika ada
    for (const [propName, propConfig] of Object.entries(this.optionalProperties)) {
      if (propName in payload) {
        this.validateProperty(payload[propName], propName, propConfig, fileName);
      }
    }

    // Validasi khusus untuk properti tertentu
    this.validateFieldNames(payload.fieldName, fileName, payload.primaryKey);

    if (payload.action) {
      this.validateActionConfig(payload.action, fileName);
    }

    // Validasi uploadConfig jika action.upload enabled
    if (payload.action && payload.action.upload && payload.uploadConfig) {
      this.validateUploadConfig(payload.uploadConfig, payload.fieldName, fileName);
    } else if (payload.action && payload.action.upload && !payload.uploadConfig) {
      throw new Error(`Payload ${fileName} has action.upload enabled but missing 'uploadConfig' property`);
    }

    // datatablesQuery: required hanya jika action.datatables enabled atau action tidak didefinisikan
    const isDatatablesEnabled = !payload.action || payload.action.datatables !== false;

    if (isDatatablesEnabled && !('datatablesQuery' in payload)) {
      throw new Error(`Payload ${fileName} must have property 'datatablesQuery' (SQL query untuk datatables) when action.datatables is enabled`);
    }

    if (payload.datatablesQuery) {
      this.validateDataTablesQuery(payload.datatablesQuery, fileName, skipSqlValidation);
    }
    
    if (payload.searchColumns) {
      this.validateSearchColumns(payload.searchColumns, fileName);
    }
    
    if (payload.filters) {
      this.validateFilters(payload.filters, fileName);
    }
    
    if (payload.advancedQueries) {
      this.validateAdvancedQueries(payload.advancedQueries, fileName);
    }
    
    if (payload.nestedQueries) {
      this.validateNestedQueries(payload.nestedQueries, fileName);
    }

    if (payload.defaultScope) {
      this.validateDefaultScope(payload.defaultScope, payload.fieldName, fileName);
    }

    if (payload.requestScope) {
      this.validateRequestScope(payload.requestScope, payload.fieldName, fileName);
    }

    if (payload.fieldProtection !== undefined) {
      throw new Error(
        `'fieldProtection' in ${fileName} has been replaced by 'fieldPolicy'. ` +
        `Example: { "fieldPolicy": { "${Object.keys(payload.fieldProtection)[0] || 'columnName'}": { "strategies": ["lock", "audit"] } } }`
      );
    }

    if (payload.fieldPolicy) {
      this.validateFieldPolicy(payload.fieldPolicy, payload.fieldName, fileName);
    }
  }

  /**
   * Validasi individual property
   * @param {*} value - Nilai property
   * @param {string} propName - Nama property
   * @param {Object} propConfig - Konfigurasi property
   * @param {string} fileName - Nama file untuk error message
   * @throws {Error} Jika property tidak valid
   */
  static validateProperty(value, propName, propConfig, fileName) {
    // Validasi type
    switch (propConfig.type) {
      case 'string':
        if (typeof value !== 'string' || value.trim().length === 0) {
          throw new Error(`Property '${propName}' in ${fileName} must be a non-empty string`);
        }
        break;

      case 'array':
        if (!Array.isArray(value) || value.length === 0) {
          throw new Error(`Property '${propName}' in ${fileName} must be a non-empty array`);
        }
        break;

      case 'object':
        if (typeof value !== 'object' || value === null) {
          throw new Error(`Property '${propName}' in ${fileName} must be an object`);
        }
        break;

      case 'mixed':
        // Tipe campuran (saat ini dipakai untuk auditColumns):
        // valid bila value adalah false, null, atau plain object (bukan array).
        // Generator template di lib/templates/*.js melakukan validasi struktur
        // lebih dalam saat emit baris this.auditColumns.
        if (value !== false && value !== null && (typeof value !== 'object' || Array.isArray(value))) {
          throw new Error(`Property '${propName}' in ${fileName} must be false, null, or object`);
        }
        break;

      default:
        throw new Error(`Unknown property type '${propConfig.type}' for ${propName}`);
    }
  }

  /**
   * Validasi defaultScope configuration
   * @param {Object} defaultScope - Object defaultScope dari payload
   * @param {Array} fieldNames - Array nama field dari payload
   * @param {string} fileName - Nama file untuk error message
   */
  static validateDefaultScope(defaultScope, fieldNames, fileName) {
    if (typeof defaultScope !== 'object' || defaultScope === null) {
      throw new Error(`Property 'defaultScope' in ${fileName} must be an object`);
    }

    const allowedActions = ['lookup', 'read'];

    for (const [actionName, scopeFilter] of Object.entries(defaultScope)) {
      if (!allowedActions.includes(actionName)) {
        console.warn(`  ⚠ Warning: defaultScope key '${actionName}' in ${fileName} is not recognized. Only 'lookup' and 'read' are supported.`);
        continue;
      }

      if (typeof scopeFilter !== 'object' || scopeFilter === null || Array.isArray(scopeFilter)) {
        throw new Error(`defaultScope.${actionName} in ${fileName} must be a non-null object`);
      }

      for (const [column, value] of Object.entries(scopeFilter)) {
        if (!fieldNames.includes(column)) {
          throw new Error(`defaultScope.${actionName} references column '${column}' which is not in fieldName of ${fileName}`);
        }

        if (typeof value !== 'boolean' && typeof value !== 'string' && typeof value !== 'number') {
          throw new Error(`defaultScope.${actionName}.${column} in ${fileName} must be a boolean, string, or number`);
        }
      }
    }
  }

  /**
   * Validasi requestScope configuration (Layer 1 RLS).
   * requestScope men-generate middleware yang inject WHERE clause dinamis
   * dari req.user saat runtime (misal app_id atau branch_id dari JWT).
   *
   * @param {Object} requestScope - Object requestScope dari payload
   * @param {Array} fieldNames - Array nama field dari payload
   * @param {string} fileName - Nama file untuk error message
   */
  static validateRequestScope(requestScope, fieldNames, fileName) {
    if (!requestScope || typeof requestScope !== 'object' || Array.isArray(requestScope)) {
      throw new Error(`requestScope in ${fileName} must be a non-null object`);
    }

    if (!requestScope.column || typeof requestScope.column !== 'string') {
      throw new Error(`requestScope.column in ${fileName} is required and must be a string`);
    }
    if (!fieldNames.includes(requestScope.column)) {
      throw new Error(
        `requestScope.column references '${requestScope.column}' which is not in fieldName of ${fileName}`
      );
    }

    if (!requestScope.source || typeof requestScope.source !== 'string') {
      throw new Error(`requestScope.source in ${fileName} is required and must be a string`);
    }
    const sourcePattern = /^req\.user\.[a-zA-Z_][a-zA-Z0-9_.]*$/;
    if (!sourcePattern.test(requestScope.source)) {
      throw new Error(
        `requestScope.source '${requestScope.source}' in ${fileName} must match format 'req.user.<property>' ` +
        `(e.g., 'req.user.app_id', 'req.user.company_id')`
      );
    }

    if (requestScope.bypassRoles !== undefined) {
      if (!Array.isArray(requestScope.bypassRoles)) {
        throw new Error(`requestScope.bypassRoles in ${fileName} must be an array of strings`);
      }
      for (const role of requestScope.bypassRoles) {
        if (typeof role !== 'string' || role.trim() === '') {
          throw new Error(`requestScope.bypassRoles in ${fileName} must contain non-empty strings`);
        }
      }
      if (requestScope.bypassRoles.length === 0) {
        console.warn(
          `  ⚠ Warning: requestScope.bypassRoles in ${fileName} is empty. ` +
          `Scope will apply to ALL users including administrators.`
        );
      }
    }

    const validKeys = ['column', 'source', 'bypassRoles'];
    for (const key of Object.keys(requestScope)) {
      if (!validKeys.includes(key)) {
        console.warn(
          `  ⚠ Warning: requestScope property '${key}' in ${fileName} is not recognized. ` +
          `Valid properties: ${validKeys.join(', ')}`
        );
      }
    }
  }

  /**
   * Validasi fieldPolicy configuration (Layer 2 concurrency-by-design,
   * strategies model). Setiap kolom mendeklarasikan array `strategies` yang
   * menentukan mekanisme proteksi yang aktif. Phase 1 mendukung dua strategy:
   *   - "lock"  : SELECT ... FOR UPDATE row lock sebelum UPDATE
   *   - "audit" : INSERT ke <tableName>_audit setelah UPDATE/adjust
   *
   * Strategy "condition" dan "version" planned untuk Phase 2 dan ditolak
   * eksplisit di Phase 1.
   *
   * @param {Object} fieldPolicy - Object fieldPolicy dari payload
   * @param {Array} fieldNames - Array nama field dari payload
   * @param {string} fileName - Nama file untuk error message
   * @throws {Error} Jika fieldPolicy tidak valid
   */
  static validateFieldPolicy(fieldPolicy, fieldNames, fileName) {
    if (!fieldPolicy || typeof fieldPolicy !== 'object' || Array.isArray(fieldPolicy)) {
      throw new Error(`fieldPolicy in ${fileName} must be a non-null object`);
    }

    const VALID_STRATEGIES_PHASE1 = ['lock', 'audit'];
    const PHASE2_STRATEGIES = ['condition', 'version'];
    const VALID_ENTRY_KEYS = ['strategies'];

    for (const [columnName, config] of Object.entries(fieldPolicy)) {
      if (!fieldNames.includes(columnName)) {
        throw new Error(
          `fieldPolicy column '${columnName}' in ${fileName} is not in fieldName`
        );
      }

      if (!config || typeof config !== 'object' || Array.isArray(config)) {
        throw new Error(
          `fieldPolicy.${columnName} in ${fileName} must be a non-null object`
        );
      }

      if (!Array.isArray(config.strategies) || config.strategies.length === 0) {
        throw new Error(
          `fieldPolicy.${columnName}.strategies in ${fileName} is required and must be a non-empty array of strings`
        );
      }

      for (const strategy of config.strategies) {
        if (typeof strategy !== 'string') {
          throw new Error(
            `fieldPolicy.${columnName}.strategies in ${fileName} is required and must be a non-empty array of strings`
          );
        }

        if (VALID_STRATEGIES_PHASE1.includes(strategy)) continue;

        if (PHASE2_STRATEGIES.includes(strategy)) {
          throw new Error(
            `fieldPolicy.${columnName}.strategies includes '${strategy}' which is planned for Phase 2 of fieldPolicy roadmap. Currently only 'lock' and 'audit' are supported.`
          );
        }

        throw new Error(
          `fieldPolicy.${columnName}.strategies includes '${strategy}' which is not valid. Valid strategies in Phase 1: ${VALID_STRATEGIES_PHASE1.join(', ')}`
        );
      }

      for (const key of Object.keys(config)) {
        if (!VALID_ENTRY_KEYS.includes(key)) {
          throw new Error(
            `fieldPolicy.${columnName}.${key} is not supported in Phase 1 of fieldPolicy roadmap`
          );
        }
      }
    }
  }

  /**
   * Validasi khusus untuk fieldName array
   * @param {Array} fieldNames - Array nama field
   * @param {string} fileName - Nama file untuk error message
   * @param {string} primaryKey - Primary key dari payload (optional)
   * @throws {Error} Jika fieldNames tidak valid
   */
  static validateFieldNames(fieldNames, fileName, primaryKey = null) {
    if (!Array.isArray(fieldNames) || fieldNames.length === 0) {
      throw new Error(`Property 'fieldName' in ${fileName} must be a non-empty array`);
    }

    // Validasi setiap field name
    for (let i = 0; i < fieldNames.length; i++) {
      const fieldName = fieldNames[i];
      
      if (typeof fieldName !== 'string' || fieldName.trim().length === 0) {
        throw new Error(`Field name at index ${i} in ${fileName} must be a non-empty string`);
      }

      // Validasi format field name (valid SQL column name)
      const validPattern = /^[a-zA-Z][a-zA-Z0-9_]*$/;
      if (!validPattern.test(fieldName.trim())) {
        throw new Error(`Field name '${fieldName}' in ${fileName} is invalid. Must start with a letter and only contain letters, numbers, and underscore`);
      }

      // Validasi panjang field name
      if (fieldName.trim().length > 63) {
        throw new Error(`Field name '${fieldName}' in ${fileName} is too long (max 63 characters for database compatibility)`);
      }
    }

    // Cek duplikasi (case insensitive)
    const uniqueFields = new Set(fieldNames.map(f => f.trim().toLowerCase()));
    if (uniqueFields.size !== fieldNames.length) {
      throw new Error(`Field names in ${fileName} cannot be duplicated (case insensitive)`);
    }

    // Pastikan ada primary key field
    // Gunakan primaryKey dari parameter, atau fallback ke field pertama
    const pkField = primaryKey || (fieldNames && fieldNames[0]) || 'id';
    const lowerCaseFields = fieldNames.map(f => f.toLowerCase());

    if (!lowerCaseFields.includes(pkField.toLowerCase())) {
      console.warn(`Warning: Primary key field '${pkField}' not found in fieldName array in ${fileName}. This may cause problems in CRUD operations`);
    }

    // Warning untuk field names yang berpotensi bermasalah
    const problematicFields = fieldNames.filter(field => {
      const lower = field.toLowerCase();
      return ['password', 'secret', 'token', 'key', 'private'].some(pattern => lower.includes(pattern));
    });

    if (problematicFields.length > 0) {
      console.warn(`Warning: Potentially sensitive field names detected in ${fileName}: ${problematicFields.join(', ')}`);
    }
  }

  /**
   * Validasi konfigurasi action
   * @param {Object} action - Action configuration
   * @param {string} fileName - Nama file untuk error message
   * @throws {Error} Jika action tidak valid
   */
  static validateActionConfig(action, fileName) {
    if (typeof action !== 'object' || action === null) {
      throw new Error(`Property 'action' in ${fileName} must be an object`);
    }

    const validActions = ['datatables', 'create', 'add', 'update', 'delete', 'first', 'lookup', 'read', 'list', 'export', 'import', 'adjust', 'aggregate', 'createComposite', 'updateComposite', 'readComposite', 'upload', 'workflow'];
    const providedActions = Object.keys(action);

    // Validasi action names
    for (const actionName of providedActions) {
      if (!validActions.includes(actionName)) {
        console.warn(`Warning: Unknown action '${actionName}' in ${fileName}. Valid actions: ${validActions.join(', ')}`);
      }

      // Validasi action value (harus boolean)
      if (typeof action[actionName] !== 'boolean') {
        throw new Error(`Action '${actionName}' in ${fileName} must be a boolean (true/false)`);
      }
    }

    // Warning jika tidak ada action yang enabled
    const enabledActions = Object.entries(action).filter(([, enabled]) => enabled).map(([name]) => name);
    if (enabledActions.length === 0) {
      console.warn(`Warning: No actions are enabled in ${fileName}. Endpoint may not be useful`);
    } else {
      console.log(`Actions to be created: ${enabledActions.join(', ')}`);
    }

    // Logical validation untuk action combinations
    if (action.update && !action.first) {
      console.warn(`Warning: Update action enabled without first action in ${fileName}. Difficult to retrieve data before update`);
    }

    if (action.delete && !action.first) {
      console.warn(`Warning: Delete action enabled without first action in ${fileName}. Difficult to verify data before delete`);
    }
  }

  /**
   * Validasi konfigurasi upload
   * @param {Object} uploadConfig - Upload configuration object
   * @param {string[]} fieldName - Array of valid field names dari payload
   * @param {string} fileName - Nama file untuk error message
   * @throws {Error} Jika uploadConfig tidak valid
   */
  static validateUploadConfig(uploadConfig, fieldName, fileName) {
    if (typeof uploadConfig !== 'object' || uploadConfig === null) {
      throw new Error(`Property 'uploadConfig' in ${fileName} must be an object`);
    }

    if (!uploadConfig.fields || typeof uploadConfig.fields !== 'object') {
      throw new Error(`Property 'uploadConfig.fields' in ${fileName} must be an object with file field definitions`);
    }

    const validColumnTypes = ['jsonb', 'json', 'clob', 'text'];

    for (const [field, config] of Object.entries(uploadConfig.fields)) {
      // Validasi field ada di fieldName
      if (!fieldName.includes(field)) {
        throw new Error(`Upload field '${field}' in ${fileName} is not listed in fieldName array`);
      }

      // Validasi columnType
      if (config.columnType && !validColumnTypes.includes(config.columnType)) {
        console.warn(`Warning: Unknown columnType '${config.columnType}' for upload field '${field}' in ${fileName}. Valid types: ${validColumnTypes.join(', ')}`);
      }

      // Validasi maxFiles
      if (config.maxFiles !== undefined) {
        if (typeof config.maxFiles !== 'number' || config.maxFiles < 1) {
          throw new Error(`Upload field '${field}' maxFiles in ${fileName} must be a positive number`);
        }
      }

      // Validasi maxFileSize
      if (config.maxFileSize !== undefined) {
        if (typeof config.maxFileSize !== 'string' || !/^\d+(?:\.\d+)?\s*(B|KB|MB|GB)$/i.test(config.maxFileSize)) {
          throw new Error(`Upload field '${field}' maxFileSize in ${fileName} must be a string like "5MB", "500KB", etc.`);
        }
      }

      // Validasi allowedTypes
      if (config.allowedTypes !== undefined) {
        if (!Array.isArray(config.allowedTypes) || config.allowedTypes.length === 0) {
          throw new Error(`Upload field '${field}' allowedTypes in ${fileName} must be a non-empty array of extensions`);
        }
      }

      console.log(`  Upload field '${field}': maxFiles=${config.maxFiles || 'unlimited'}, maxSize=${config.maxFileSize || '10MB'}, types=${(config.allowedTypes || ['*']).join(',')}`);
    }
  }

  /**
   * Validasi DataTables query
   * @param {string} query - SQL query untuk datatables
   * @param {string} fileName - Nama file untuk error message
   * @param {boolean} skipSqlValidation - Skip validasi SQL keywords (default: false)
   * @throws {Error} Jika query tidak valid
   */
  static validateDataTablesQuery(query, fileName, skipSqlValidation = false) {
    if (typeof query !== 'string' || query.trim().length === 0) {
      throw new Error(`Property 'datatablesQuery' in ${fileName} must be a non-empty string`);
    }

    // Check for file reference first
    if (query.startsWith('file:')) {
      this.validateFileReference(query, fileName, 'datatablesQuery');
      console.log('DataTables query (file reference) valid');
      return;
    }

    const trimmedQuery = query.trim().toLowerCase();

    // Basic SQL validation for inline queries
    if (!trimmedQuery.startsWith('select') && !trimmedQuery.startsWith('with')) {
      throw new Error(`DataTables query in ${fileName} must start with SELECT or WITH`);
    }

    // Check for dangerous SQL keywords (basic protection)
    // Skip jika skipSqlValidation = true (untuk keperluan generator)
    if (!skipSqlValidation) {
      // Hanya validasi keyword yang benar-benar berbahaya dalam konteks DataTables query
      // Hindari false positive dengan mengecek context yang lebih spesifik
      const dangerousPatterns = [
        /\bdrop\s+table\b/i,
        /\bdrop\s+database\b/i,
        /\btruncate\s+table\b/i,
        /\bdelete\s+from\b/i,
        /\binsert\s+into\b/i,
        /\bupdate\s+\w+\s+set\b/i,
        /\balter\s+table\b/i,
        /\bcreate\s+table\b/i,
        /\bcreate\s+database\b/i
      ];
      
      for (const pattern of dangerousPatterns) {
        if (pattern.test(trimmedQuery)) {
          throw new Error(`DataTables query in ${fileName} contains dangerous keyword: ${pattern.source}`);
        }
      }
    } else {
      console.log('SQL validation skipped for generator purposes');
    }

    console.log('DataTables query valid');
  }

  /**
   * Validasi search columns configuration
   * @param {Array} searchColumns - Search columns array
   * @param {string} fileName - Nama file untuk error message
   * @throws {Error} Jika search columns tidak valid
   */
  static validateSearchColumns(searchColumns, fileName) {
    if (!Array.isArray(searchColumns)) {
      throw new Error(`Property 'searchColumns' di ${fileName} harus berupa array`);
    }

    for (let i = 0; i < searchColumns.length; i++) {
      const column = searchColumns[i];
      
      if (typeof column !== 'object' || column === null) {
        throw new Error(`Search column index ${i} di ${fileName} harus berupa object`);
      }

      if (!column.name || typeof column.name !== 'string') {
        throw new Error(`Search column index ${i} di ${fileName} harus memiliki property 'name' berupa string`);
      }

      if (column.searchable !== undefined && typeof column.searchable !== 'boolean') {
        throw new Error(`Search column '${column.name}' di ${fileName} property 'searchable' harus boolean`);
      }
    }
  }

  /**
   * Validasi filters configuration
   * @param {Array} filters - Filters array
   * @param {string} fileName - Nama file untuk error message
   * @throws {Error} Jika filters tidak valid
   */
  static validateFilters(filters, fileName) {
    if (!Array.isArray(filters)) {
      throw new Error(`Property 'filters' di ${fileName} harus berupa array`);
    }

    const validFilterTypes = ['combobox', 'select', 'daterange', 'text', 'number'];

    for (let i = 0; i < filters.length; i++) {
      const filter = filters[i];
      
      if (typeof filter !== 'object' || filter === null) {
        throw new Error(`Filter index ${i} di ${fileName} harus berupa object`);
      }

      // Required properties
      if (!filter.name || typeof filter.name !== 'string') {
        throw new Error(`Filter index ${i} di ${fileName} harus memiliki property 'name' berupa string`);
      }

      if (!filter.type || typeof filter.type !== 'string') {
        throw new Error(`Filter '${filter.name}' di ${fileName} harus memiliki property 'type' berupa string`);
      }

      if (!validFilterTypes.includes(filter.type)) {
        throw new Error(`Filter '${filter.name}' di ${fileName} memiliki type tidak valid: ${filter.type}. Valid types: ${validFilterTypes.join(', ')}`);
      }

      // Type-specific validation
      if (['combobox', 'select'].includes(filter.type)) {
        if (!filter.targetField || typeof filter.targetField !== 'string') {
          throw new Error(`Filter '${filter.name}' type '${filter.type}' di ${fileName} harus memiliki property 'targetField'`);
        }
      }

      if (filter.type === 'daterange') {
        if (!filter.startField || typeof filter.startField !== 'string') {
          throw new Error(`Filter '${filter.name}' type 'daterange' di ${fileName} harus memiliki property 'startField'`);
        }
        if (!filter.endField || typeof filter.endField !== 'string') {
          throw new Error(`Filter '${filter.name}' type 'daterange' di ${fileName} harus memiliki property 'endField'`);
        }
      }
    }
  }

  /**
   * Validasi advanced queries configuration
   * @param {Object} advancedQueries - Advanced queries object
   * @param {string} fileName - Nama file untuk error message
   * @throws {Error} Jika advanced queries tidak valid
   */
  static validateAdvancedQueries(advancedQueries, fileName) {
    if (typeof advancedQueries !== 'object' || advancedQueries === null) {
      throw new Error(`Property 'advancedQueries' di ${fileName} harus berupa object`);
    }

    for (const [queryName, querySource] of Object.entries(advancedQueries)) {
      if (typeof querySource !== 'string' || querySource.trim().length === 0) {
        throw new Error(`Advanced query '${queryName}' di ${fileName} harus berupa string non-empty`);
      }

      // Validate query name
      if (!/^[a-zA-Z][a-zA-Z0-9_]*$/.test(queryName)) {
        throw new Error(`Advanced query name '${queryName}' di ${fileName} tidak valid. Harus dimulai dengan huruf dan hanya mengandung huruf, angka, underscore`);
      }

      // Validate file reference if applicable
      if (querySource.startsWith('file:')) {
        this.validateFileReference(querySource, fileName, `advancedQueries.${queryName}`);
      } else {
        // Validate inline SQL
        this.validateInlineSQL(querySource, fileName, `advancedQueries.${queryName}`);
      }
    }
  }

  /**
   * Validasi nested queries configuration
   * @param {Array} nestedQueries - Nested queries array
   * @param {string} fileName - Nama file untuk error message
   * @throws {Error} Jika nested queries tidak valid
   */
  static validateNestedQueries(nestedQueries, fileName) {
    if (!Array.isArray(nestedQueries)) {
      throw new Error(`Property 'nestedQueries' di ${fileName} harus berupa array`);
    }

    for (let i = 0; i < nestedQueries.length; i++) {
      const nestedQuery = nestedQueries[i];
      
      if (typeof nestedQuery !== 'object' || nestedQuery === null) {
        throw new Error(`Nested query index ${i} di ${fileName} harus berupa object`);
      }

      if (!nestedQuery.name || typeof nestedQuery.name !== 'string') {
        throw new Error(`Nested query index ${i} di ${fileName} harus memiliki property 'name' berupa string`);
      }

      if (!nestedQuery.query || typeof nestedQuery.query !== 'string') {
        throw new Error(`Nested query '${nestedQuery.name}' di ${fileName} harus memiliki property 'query' berupa string`);
      }

      // Validate file reference if applicable
      if (nestedQuery.query.startsWith('file:')) {
        this.validateFileReference(nestedQuery.query, fileName, `nestedQueries[${i}].query`);
      } else {
        // Validate inline SQL
        this.validateInlineSQL(nestedQuery.query, fileName, `nestedQueries[${i}].query`);
      }
    }
  }

  /**
   * Validasi file reference format
   * @param {string} fileReference - File reference string
   * @param {string} fileName - Nama file untuk error message
   * @param {string} context - Context untuk error message
   * @throws {Error} Jika file reference tidak valid
   */
  static validateFileReference(fileReference, fileName, context) {
    if (!fileReference.startsWith('file:')) {
      throw new Error(`File reference untuk ${context} di ${fileName} harus dimulai dengan 'file:'`);
    }

    const relativePath = fileReference.substring(5);
    
    if (!relativePath || relativePath.trim().length === 0) {
      throw new Error(`File reference untuk ${context} di ${fileName} tidak memiliki path yang valid`);
    }

    // Validate file extension - allow both .sql and .json for queries
    if (!relativePath.endsWith('.sql') && !relativePath.endsWith('.json')) {
      console.warn(`Warning: File reference ${relativePath} for ${context} in ${fileName} should have .sql or .json extension`);
    }

    // Validate path format (no absolute paths, no parent directory traversal)
    if (relativePath.startsWith('/') || relativePath.includes('..')) {
      throw new Error(`File reference ${relativePath} for ${context} in ${fileName} must not use absolute path or parent directory traversal`);
    }

    console.log(`File reference valid: ${relativePath} for ${context}`);
  }

  /**
   * Validasi inline SQL content
   * @param {string} sqlContent - SQL content
   * @param {string} fileName - Nama file untuk error message
   * @param {string} context - Context untuk error message
   * @throws {Error} Jika SQL tidak valid
   */
  static validateInlineSQL(sqlContent, fileName, context) {
    const trimmedSql = sqlContent.trim().toLowerCase();

    if (!trimmedSql.startsWith('select') && !trimmedSql.startsWith('with')) {
      console.warn(`Warning: Inline SQL for ${context} in ${fileName} does not start with SELECT or WITH`);
    }

    // Check for dangerous keywords
    // Gunakan regex pattern untuk mendeteksi keyword berbahaya dengan lebih akurat
    const dangerousPatterns = [
      /\bdrop\s+table\b/i,
      /\bdrop\s+database\b/i,
      /\btruncate\s+table\b/i,
      /\bdelete\s+from\b/i,
      /\binsert\s+into\b/i,
      /\bupdate\s+\w+\s+set\b/i,
      /\balter\s+table\b/i,
      /\bcreate\s+table\b/i,
      /\bcreate\s+database\b/i
    ];
    
    for (const pattern of dangerousPatterns) {
      if (pattern.test(trimmedSql)) {
        throw new Error(`Inline SQL untuk ${context} di ${fileName} mengandung keyword berbahaya: ${pattern.source}`);
      }
    }
  }

  /**
   * Validasi payload untuk kompatibilitas database
   * @param {Object} payload - Payload object
   * @param {string} databaseType - Tipe database (postgres/oracle)
   * @returns {Object} Validation result dengan warnings/recommendations
   */
  static validateDatabaseCompatibility(payload, databaseType) {
    const result = {
      isCompatible: true,
      warnings: [],
      recommendations: []
    };

    if (databaseType === 'oracle') {
      // Oracle-specific validations
      
      // Check for PostgreSQL-specific syntax in queries
      const queries = this.extractAllQueries(payload);
      for (const [queryPath, queryContent] of Object.entries(queries)) {
        if (queryContent.includes('LIMIT') || queryContent.includes('OFFSET')) {
          result.warnings.push(`Query ${queryPath} menggunakan LIMIT/OFFSET (PostgreSQL syntax). Oracle menggunakan ROWNUM/FETCH`);
        }
        
        if (queryContent.includes('$1') || queryContent.includes('$2')) {
          result.warnings.push(`Query ${queryPath} menggunakan parameter PostgreSQL ($1, $2). Oracle menggunakan :1, :2`);
        }
      }

      // Check field names for Oracle reserved words
      const oracleReservedWords = [
        'user', 'order', 'group', 'size', 'comment', 'date', 'number', 'varchar2',
        'table', 'index', 'trigger', 'sequence', 'view', 'procedure', 'function'
      ];
      
      if (payload.fieldName) {
        const conflictingFields = payload.fieldName.filter(field => 
          oracleReservedWords.includes(field.toLowerCase())
        );
        
        if (conflictingFields.length > 0) {
          result.warnings.push(`Field names berpotensi konflik dengan Oracle reserved words: ${conflictingFields.join(', ')}`);
          result.recommendations.push('Pertimbangkan untuk mengganti nama field atau gunakan quoted identifiers');
        }
      }

      result.recommendations.push('Pastikan utils/oracle.js tersedia untuk template Oracle');
    }

    if (databaseType === 'postgres') {
      // PostgreSQL-specific validations
      
      // Check for Oracle-specific syntax
      const queries = this.extractAllQueries(payload);
      for (const [queryPath, queryContent] of Object.entries(queries)) {
        if (queryContent.includes('ROWNUM') || queryContent.includes('FETCH FIRST')) {
          result.warnings.push(`Query ${queryPath} menggunakan syntax Oracle (ROWNUM/FETCH). PostgreSQL menggunakan LIMIT/OFFSET`);
        }
      }
    }

    return result;
  }

  /**
   * Extract semua queries dari payload untuk validation
   * @param {Object} payload - Payload object
   * @returns {Object} Object dengan query paths dan content
   */
  static extractAllQueries(payload) {
    const queries = {};

    if (payload.datatablesQuery && !payload.datatablesQuery.startsWith('file:')) {
      queries['datatablesQuery'] = payload.datatablesQuery;
    }

    if (payload.advancedQueries) {
      for (const [queryName, querySource] of Object.entries(payload.advancedQueries)) {
        if (!querySource.startsWith('file:')) {
          queries[`advancedQueries.${queryName}`] = querySource;
        }
      }
    }

    if (payload.nestedQueries) {
      payload.nestedQueries.forEach((nestedQuery, index) => {
        if (nestedQuery.query && !nestedQuery.query.startsWith('file:')) {
          queries[`nestedQueries[${index}].query`] = nestedQuery.query;
        }
      });
    }

    return queries;
  }

  /**
   * Generate summary dari payload yang telah divalidasi
   * @param {Object} payload - Payload object
   * @returns {Object} Summary informasi
   */
  static generatePayloadSummary(payload) {
    const summary = {
      tableName: payload.tableName,
      fieldCount: payload.fieldName ? payload.fieldName.length : 0,
      fields: payload.fieldName || [],
      hasActions: !!(payload.action && Object.keys(payload.action).length > 0),
      enabledActions: [],
      hasAdvancedQueries: !!(payload.advancedQueries && Object.keys(payload.advancedQueries).length > 0),
      hasNestedQueries: !!(payload.nestedQueries && payload.nestedQueries.length > 0),
      hasFilters: !!(payload.filters && payload.filters.length > 0),
      hasSearchColumns: !!(payload.searchColumns && payload.searchColumns.length > 0),
      hasFileReferences: this.hasFileReferences(payload)
    };

    // Extract enabled actions
    if (payload.action) {
      summary.enabledActions = Object.entries(payload.action)
        .filter(([, enabled]) => enabled)
        .map(([name]) => name);
    }

    return summary;
  }

  /**
   * Check apakah payload memiliki file references
   * @param {Object} payload - Payload object
   * @returns {boolean} True jika ada file references
   */
  static hasFileReferences(payload) {
    if (payload.datatablesQuery && payload.datatablesQuery.startsWith('file:')) {
      return true;
    }

    if (payload.advancedQueries) {
      for (const querySource of Object.values(payload.advancedQueries)) {
        if (typeof querySource === 'string' && querySource.startsWith('file:')) {
          return true;
        }
      }
    }

    if (payload.nestedQueries) {
      for (const nestedQuery of payload.nestedQueries) {
        if (nestedQuery.query && nestedQuery.query.startsWith('file:')) {
          return true;
        }
      }
    }

    if (payload.customQueries) {
      for (const queries of Object.values(payload.customQueries)) {
        if (typeof queries === 'string' && queries.startsWith('file:')) {
          return true;
        }
        if (typeof queries === 'object') {
          for (const query of Object.values(queries)) {
            if (typeof query === 'string' && query.startsWith('file:')) {
              return true;
            }
          }
        }
      }
    }

    return false;
  }

  /**
   * Show detailed payload summary
   * @param {Object} payload - Payload object
   * @param {string} fileName - Nama file payload
   */
  static showPayloadSummary(payload, fileName) {
    const summary = this.generatePayloadSummary(payload);
    
    console.log('Payload Summary:');
    console.log(`   File: ${fileName}`);
    console.log(`   Table: ${summary.tableName}`);
    console.log(`   Fields: ${summary.fieldCount} (${summary.fields.slice(0, 5).join(', ')}${summary.fieldCount > 5 ? '...' : ''})`);
    
    if (summary.hasActions) {
      console.log(`   Actions: ${summary.enabledActions.join(', ')}`);
    } else {
      console.log(`   Actions: None defined`);
    }
    
    if (summary.hasAdvancedQueries) {
      console.log(`   Advanced Queries: Yes`);
    }
    
    if (summary.hasNestedQueries) {
      console.log(`   Nested Queries: Yes`);
    }
    
    if (summary.hasFilters) {
      console.log(`   Filters: Yes`);
    }
    
    if (summary.hasSearchColumns) {
      console.log(`   Search Columns: Yes`);
    }

    if (summary.hasFileReferences) {
      console.log(`   File References: Yes (requires processing)`);
    }
    
    console.log('');
  }

  /**
   * Validasi payload untuk requirements khusus
   * @param {Object} payload - Payload object
   * @param {Object} requirements - Requirements object
   * @returns {boolean} True jika memenuhi requirements
   * @throws {Error} Jika tidak memenuhi requirements
   */
  static validateRequirements(payload, requirements) {
    if (!requirements || typeof requirements !== 'object') {
      return true;
    }

    // Check required actions
    if (requirements.requiredActions && Array.isArray(requirements.requiredActions)) {
      const enabledActions = Object.entries(payload.action || {})
        .filter(([, enabled]) => enabled)
        .map(([name]) => name);
        
      const missingActions = requirements.requiredActions.filter(
        action => !enabledActions.includes(action)
      );
      
      if (missingActions.length > 0) {
        throw new Error(`Payload harus memiliki actions berikut: ${missingActions.join(', ')}`);
      }
    }

    // Check required fields
    if (requirements.requiredFields && Array.isArray(requirements.requiredFields)) {
      const fieldNames = (payload.fieldName || []).map(f => f.toLowerCase());
      const missingFields = requirements.requiredFields.filter(
        field => !fieldNames.includes(field.toLowerCase())
      );
      
      if (missingFields.length > 0) {
        throw new Error(`Payload harus memiliki fields berikut: ${missingFields.join(', ')}`);
      }
    }

    // Check minimum field count
    if (requirements.minFieldCount && typeof requirements.minFieldCount === 'number') {
      const fieldCount = payload.fieldName ? payload.fieldName.length : 0;
      if (fieldCount < requirements.minFieldCount) {
        throw new Error(`Payload harus memiliki minimal ${requirements.minFieldCount} fields, ditemukan ${fieldCount}`);
      }
    }

    // Check maximum field count
    if (requirements.maxFieldCount && typeof requirements.maxFieldCount === 'number') {
      const fieldCount = payload.fieldName ? payload.fieldName.length : 0;
      if (fieldCount > requirements.maxFieldCount) {
        throw new Error(`Payload tidak boleh memiliki lebih dari ${requirements.maxFieldCount} fields, ditemukan ${fieldCount}`);
      }
    }

    // Check required table name pattern
    if (requirements.tableNamePattern && typeof requirements.tableNamePattern === 'string') {
      const pattern = new RegExp(requirements.tableNamePattern);
      if (!pattern.test(payload.tableName)) {
        throw new Error(`Table name '${payload.tableName}' tidak sesuai dengan pattern yang diperlukan: ${requirements.tableNamePattern}`);
      }
    }

    return true;
  }

  /**
   * Comprehensive validation untuk full payload
   * @param {Object} payload - Payload object
   * @param {string} fileName - Nama file
   * @param {Object} options - Validation options
   * @returns {Object} Validation result
   */
  static comprehensiveValidation(payload, fileName, options = {}) {
    const result = {
      isValid: true,
      errors: [],
      warnings: [],
      summary: null,
      databaseCompatibility: null
    };

    try {
      // 1. Basic structure validation
      this.validatePayloadStructure(payload, fileName);
      
      // 2. Database compatibility validation
      if (options.databaseType) {
        result.databaseCompatibility = this.validateDatabaseCompatibility(payload, options.databaseType);
        result.warnings.push(...result.databaseCompatibility.warnings);
      }
      
      // 3. Requirements validation
      if (options.requirements) {
        this.validateRequirements(payload, options.requirements);
      }
      
      // 4. Generate summary
      result.summary = this.generatePayloadSummary(payload);
      
      console.log('Comprehensive validation passed');
      
    } catch (error) {
      result.isValid = false;
      result.errors.push(error.message);
      console.error(`Validation failed: ${error.message}`);
    }

    return result;
  }

  /**
   * Quick validation untuk basic checks
   * @param {Object} payload - Payload object
   * @returns {boolean} True jika basic validation berhasil
   */
  static quickValidation(payload) {
    try {
      // Check required properties exist
      const requiredProps = Object.keys(this.requiredProperties);
      for (const prop of requiredProps) {
        if (!(prop in payload)) {
          return false;
        }
      }

      // Check basic types
      if (typeof payload.tableName !== 'string' || payload.tableName.trim().length === 0) {
        return false;
      }

      if (!Array.isArray(payload.fieldName) || payload.fieldName.length === 0) {
        return false;
      }

      // datatablesQuery hanya required jika action.datatables enabled atau action tidak didefinisikan
      const isDatatablesEnabled = !payload.action || payload.action.datatables !== false;
      if (isDatatablesEnabled) {
        if (typeof payload.datatablesQuery !== 'string' || payload.datatablesQuery.trim().length === 0) {
          return false;
        }
      }

      return true;
    } catch (error) {
      return false;
    }
  }

  /**
   * Validate payload file existence dan basic format
   * @param {string} payloadFileName - Nama file payload
   * @returns {Object} File validation result
   */
  static validatePayloadFile(payloadFileName) {
    const result = {
      exists: false,
      isValidJson: false,
      path: null,
      size: 0,
      errors: []
    };

    try {
      // Check file existence
      const payloadPath = this.findPayloadFile(payloadFileName);
      if (!payloadPath) {
        result.errors.push('File payload tidak ditemukan');
        return result;
      }

      result.exists = true;
      result.path = payloadPath;

      // Check file size
      const stats = require('fs').statSync(payloadPath);
      result.size = stats.size;

      if (result.size === 0) {
        result.errors.push('File payload kosong');
        return result;
      }

      if (result.size > 10 * 1024 * 1024) { // 10MB limit
        result.errors.push('File payload terlalu besar (>10MB)');
        return result;
      }

      // Check JSON format
      try {
        const content = require('fs').readFileSync(payloadPath, 'utf8');
        JSON.parse(content);
        result.isValidJson = true;
      } catch (jsonError) {
        result.errors.push(`Format JSON tidak valid: ${jsonError.message}`);
      }

    } catch (error) {
      result.errors.push(`Error checking file: ${error.message}`);
    }

    return result;
  }

  /**
   * Get validation schema untuk documentation
   * @returns {Object} Validation schema object
   */
  static getValidationSchema() {
    return {
      required: this.requiredProperties,
      optional: this.optionalProperties,
      validActions: ['datatables', 'create', 'add', 'update', 'delete', 'first', 'lookup', 'read', 'list', 'export', 'import', 'adjust', 'aggregate', 'createComposite', 'updateComposite', 'readComposite', 'upload', 'workflow'],
      validFilterTypes: ['combobox', 'select', 'daterange', 'text', 'number'],
      fieldNamePattern: '^[a-zA-Z][a-zA-Z0-9_]* ',
      maxFieldNameLength: 63,
      supportedDatabases: ['postgres', 'oracle'],
      fileReferencePattern: '^file:[^/].*\\.sql '
    };
  }

  /**
   * Generate validation report untuk debugging
   * @param {Object} payload - Payload object
   * @param {string} fileName - Nama file
   * @returns {Object} Detailed validation report
   */
  static generateValidationReport(payload, fileName) {
    const report = {
      fileName: fileName,
      timestamp: new Date().toISOString(),
      basicInfo: {
        hasRequiredProperties: true,
        missingRequired: [],
        extraProperties: []
      },
      fieldAnalysis: {
        totalFields: 0,
        validFields: 0,
        invalidFields: [],
        duplicateFields: [],
        sensitiveFields: []
      },
      queryAnalysis: {
        totalQueries: 0,
        fileReferences: 0,
        inlineQueries: 0,
        dangerousKeywords: []
      },
      actionAnalysis: {
        totalActions: 0,
        enabledActions: [],
        disabledActions: [],
        recommendedActions: []
      }
    };

    try {
      // Basic properties analysis
      const requiredProps = Object.keys(this.requiredProperties);
      const payloadProps = Object.keys(payload);

      report.basicInfo.missingRequired = requiredProps.filter(prop => !payloadProps.includes(prop));
      report.basicInfo.extraProperties = payloadProps.filter(prop => 
        !requiredProps.includes(prop) && 
        !Object.keys(this.optionalProperties).includes(prop)
      );
      report.basicInfo.hasRequiredProperties = report.basicInfo.missingRequired.length === 0;

      // Field analysis
      if (payload.fieldName && Array.isArray(payload.fieldName)) {
        report.fieldAnalysis.totalFields = payload.fieldName.length;
        
        const fieldPattern = /^[a-zA-Z][a-zA-Z0-9_]*$/;
        const sensitivePatterns = ['password', 'secret', 'token', 'key', 'private'];
        
        payload.fieldName.forEach(field => {
          if (fieldPattern.test(field)) {
            report.fieldAnalysis.validFields++;
          } else {
            report.fieldAnalysis.invalidFields.push(field);
          }

          if (sensitivePatterns.some(pattern => field.toLowerCase().includes(pattern))) {
            report.fieldAnalysis.sensitiveFields.push(field);
          }
        });

        // Check duplicates
        const lowerFields = payload.fieldName.map(f => f.toLowerCase());
        const uniqueFields = [...new Set(lowerFields)];
        if (uniqueFields.length !== payload.fieldName.length) {
          report.fieldAnalysis.duplicateFields = payload.fieldName.filter((field, index) => 
            lowerFields.indexOf(field.toLowerCase()) !== index
          );
        }
      }

      // Query analysis
      const allQueries = this.extractAllQueries(payload);
      report.queryAnalysis.totalQueries = Object.keys(allQueries).length;

      if (payload.datatablesQuery && payload.datatablesQuery.startsWith('file:')) {
        report.queryAnalysis.fileReferences++;
      } else if (payload.datatablesQuery) {
        report.queryAnalysis.inlineQueries++;
      }

      // Action analysis
      if (payload.action) {
        const actions = Object.entries(payload.action);
        report.actionAnalysis.totalActions = actions.length;
        report.actionAnalysis.enabledActions = actions.filter(([,enabled]) => enabled).map(([name]) => name);
        report.actionAnalysis.disabledActions = actions.filter(([,enabled]) => !enabled).map(([name]) => name);
        
        // Recommendations
        if (!report.actionAnalysis.enabledActions.includes('first') &&
            (report.actionAnalysis.enabledActions.includes('update') || report.actionAnalysis.enabledActions.includes('delete'))) {
          report.actionAnalysis.recommendedActions.push('first');
        }
      }

    } catch (error) {
      report.error = error.message;
    }

    return report;
  }
}

module.exports = PayloadValidator;