import { isNil, isObject, cloneDeep } from './utils';
import { CleanerOptions, AnyObject, FileFormat, ParserOptions } from './types';
import { parseYAML, parseCSV, stringifyYAML, stringifyCSV } from './parsers';

const INITIAL_OPTIONS: Required<Omit<CleanerOptions, 'customCleaner'>> & Pick<CleanerOptions, 'customCleaner'> = {
  customCleaner: undefined,
  keepFields: [],
  recursive: true,
  safe: true
};

const evaluateEmptiness = <T>(value: T): boolean => (
  isNil(value) ||
  value === '' ||
  (Array.isArray(value) && !value.length) ||
  (isObject(value) && !Object.keys(value as object).length)
);

function processNestedObject<T extends AnyObject>(
  target: T,
  customCleaner: CleanerOptions['customCleaner'],
  preservedFields: string[],
  config: Required<CleanerOptions>
): T {
  const workingCopy = config.safe ? cloneDeep(target) : target;
  
  for (const key in workingCopy) {
    const value = workingCopy[key];
    
    if (preservedFields.includes(key)) continue;
    
    if (customCleaner?.(key, value)) {
      delete workingCopy[key];
      continue;
    }
    
    if (evaluateEmptiness(value)) {
      delete workingCopy[key];
      continue;
    }
    
    if (config.recursive && isObject(value) && !Array.isArray(value)) {
      const processed = processNestedObject(
        value as T[Extract<keyof T, string>] & AnyObject,
        customCleaner,
        preservedFields,
        config
      );
      
      if (!Object.keys(processed).length) {
        delete workingCopy[key];
      } else {
        workingCopy[key] = processed as T[Extract<keyof T, string>];
      }
    }
  }
  
  return workingCopy;
}

export function cleanObject<T extends AnyObject>(
  obj: T,
  customCleaner?: CleanerOptions['customCleaner'],
  keepFields: string[] = [],
  options: CleanerOptions = {}
): T {
  const config = { ...INITIAL_OPTIONS, ...options } as Required<CleanerOptions>;
  return processNestedObject(obj, customCleaner, keepFields, config);
}

export function parseContent(content: string, format: FileFormat, options: ParserOptions = {}): AnyObject | AnyObject[] {
  switch (format) {
    case 'yaml':
      return parseYAML(content) as AnyObject;
    case 'csv':
      return parseCSV(content, { delimiter: options.delimiter || ',', headers: options.headers !== false }) as AnyObject[];
    default:
      return JSON.parse(content);
  }
}

export function stringifyContent(data: AnyObject | AnyObject[], format: FileFormat, options: ParserOptions = {}): string {
  switch (format) {
    case 'yaml':
      return stringifyYAML(data as AnyObject);
    case 'csv':
      return stringifyCSV(Array.isArray(data) ? data : [data], {
        delimiter: options.delimiter || ',',
        headers: options.headers !== false
      });
    default:
      return JSON.stringify(data, null, 2);
  }
}

export { parseYAML, parseCSV, stringifyYAML, stringifyCSV };
export default cleanObject; 