/**
 * Frappe Expression Evaluator
 * 
 * This utility evaluates Frappe's conditional expressions like:
 * - "eval:doc.field_name == 'value'"
 * - "eval:!doc.__islocal"
 * - "eval:doc.is_existing_asset == 1"
 * - "eval:doc.company && doc.company.startsWith('Mobile')"
 * - "field_name" (simple field check - truthy)
 * - "eval:(doc.is_existing_asset)" 
 */

export interface FieldConfig {
  fieldname: string;
  label?: string;
  fieldtype: string;
  options?: string;
  reqd?: number | boolean;
  hidden?: number | boolean;
  read_only?: number | boolean;
  depends_on?: string;
  mandatory_depends_on?: string;
  read_only_depends_on?: string;
  fetch_from?: string;
  fetch_if_empty?: number | boolean;
  default?: string;
  description?: string;
  in_list_view?: number | boolean;
  in_standard_filter?: number | boolean;
  permlevel?: number;
  allow_on_submit?: number | boolean;
}

export interface EvaluatedFieldState {
  isVisible: boolean;
  isReadOnly: boolean;
  isMandatory: boolean;
}

/**
 * Safely evaluates a Frappe expression
 * @param expression - The expression string (e.g., "eval:doc.field == 1")
 * @param doc - The document object to evaluate against
 * @returns boolean result of the expression
 */
export function evaluateFrappeExpression(expression: string | undefined, doc: Record<string, any>): boolean {
  if (!expression) return false;
  
  // Trim whitespace
  expression = expression.trim();
  
  // Empty string = false
  if (!expression) return false;
  
  // Simple field reference (not an eval expression)
  if (!expression.startsWith('eval:')) {
    // Check if it's a simple field name - return truthy value of that field
    const fieldValue = doc[expression];
    return !!fieldValue;
  }
  
  // Extract the actual expression after "eval:"
  const evalExpression = expression.substring(5).trim();
  
  try {
    // Create a safe evaluation context
    // We need to handle various Frappe expression patterns:
    // - doc.field_name
    // - doc.__islocal
    // - doc.field_name == 'value'
    // - !doc.field_name
    // - doc.field && doc.field2
    // - doc.field || doc.field2
    // - doc.field.startsWith('value')
    
    // Create the evaluation function with doc in scope
    // Using Function constructor for dynamic evaluation (safer than eval)
    const evalFunc = new Function('doc', `
      try {
        return Boolean(${evalExpression});
      } catch (e) {
        console.warn('Expression evaluation error:', e);
        return false;
      }
    `);
    
    return evalFunc(doc);
  } catch (error) {
    console.warn(`Failed to evaluate expression: ${expression}`, error);
    return false;
  }
}

/**
 * Evaluates all field states (visible, readonly, mandatory) based on field config
 * @param fieldConfig - The field configuration from Frappe
 * @param doc - The current document data
 * @returns EvaluatedFieldState
 */
export function evaluateFieldState(fieldConfig: FieldConfig, doc: Record<string, any>): EvaluatedFieldState {
  // Base states from field config
  let isVisible = !(fieldConfig.hidden === 1 || fieldConfig.hidden === true);
  let isReadOnly = fieldConfig.read_only === 1 || fieldConfig.read_only === true;
  let isMandatory = fieldConfig.reqd === 1 || fieldConfig.reqd === true;
  
  // Evaluate depends_on (visibility)
  if (fieldConfig.depends_on) {
    isVisible = isVisible && evaluateFrappeExpression(fieldConfig.depends_on, doc);
  }
  
  // Evaluate mandatory_depends_on (conditional mandatory)
  if (fieldConfig.mandatory_depends_on) {
    const conditionalMandatory = evaluateFrappeExpression(fieldConfig.mandatory_depends_on, doc);
    isMandatory = isMandatory || conditionalMandatory;
  }
  
  // Evaluate read_only_depends_on (conditional read-only)
  if (fieldConfig.read_only_depends_on) {
    const conditionalReadOnly = evaluateFrappeExpression(fieldConfig.read_only_depends_on, doc);
    isReadOnly = isReadOnly || conditionalReadOnly;
  }
  
  return {
    isVisible,
    isReadOnly,
    isMandatory
  };
}

/**
 * Parses fetch_from expression to get the linked doctype and field
 * @param fetchFrom - e.g., "production_item.item_name" or "company.default_currency"
 * @returns { linkField: string, targetField: string } or null
 */
export function parseFetchFrom(fetchFrom: string | undefined): { linkField: string; targetField: string } | null {
  if (!fetchFrom) return null;
  
  const parts = fetchFrom.split('.');
  if (parts.length !== 2) return null;
  
  return {
    linkField: parts[0],
    targetField: parts[1]
  };
}

/**
 * Gets the default value for a field, evaluating if it's an expression
 * @param defaultValue - The default value string
 * @param doc - The current document
 * @returns The resolved default value
 */
export function getDefaultValue(defaultValue: string | undefined, doc: Record<string, any>): any {
  if (!defaultValue) return undefined;
  
  // Common Frappe defaults
  if (defaultValue === 'Today' || defaultValue === 'today') {
    return new Date().toISOString().split('T')[0];
  }
  
  if (defaultValue === 'Now' || defaultValue === 'now') {
    return new Date().toISOString();
  }
  
  // Check if it's a number
  if (!isNaN(Number(defaultValue))) {
    return Number(defaultValue);
  }
  
  // Return as-is for strings
  return defaultValue;
}

/**
 * Maps Frappe fieldtype to HTML input type
 */
export function getInputType(fieldtype: string): string {
  switch (fieldtype) {
    case 'Data':
    case 'Small Text':
    case 'Text':
    case 'Long Text':
      return 'text';
    case 'Int':
    case 'Float':
    case 'Currency':
    case 'Percent':
      return 'number';
    case 'Date':
      return 'date';
    case 'Datetime':
      return 'datetime-local';
    case 'Time':
      return 'time';
    case 'Check':
      return 'checkbox';
    case 'Password':
      return 'password';
    case 'Link':
    case 'Select':
      return 'select';
    case 'Attach':
    case 'Attach Image':
      return 'file';
    case 'Read Only':
    case 'HTML':
      return 'readonly';
    default:
      return 'text';
  }
}

/**
 * Parses Select field options string into array
 */
export function parseSelectOptions(options: string | undefined): string[] {
  if (!options) return [];
  return options.split('\n').filter(opt => opt.trim() !== '');
}

