#!/usr/bin/env node

import fs from 'fs';
import path from 'path';
import chalk from 'chalk';
import { cleanObject } from './index';
import { parseYAML, parseCSV, stringifyYAML, stringifyCSV } from './parsers';
import { FileFormat, ParserOptions, AnyObject } from './types';

interface CliOptions {
  inputFile: string;
  outputFile: string | null;
  compareMode: boolean;
  safeMode: boolean;
  format: FileFormat;
  convertTo: FileFormat | null;
  delimiter: string;
  headers: boolean;
  removeZeroValues: boolean;
  noClean: boolean;
}

const VALID_FORMATS = ['json', 'yaml', 'csv'] as const;
type FormatExtensions = Record<FileFormat, readonly string[]>;

const FORMAT_EXTENSIONS: FormatExtensions = {
  json: ['json'],
  yaml: ['yaml', 'yml'],
  csv: ['csv']
} as const;

const detectFileFormat = (filename: string): FileFormat => {
  const ext = path.extname(filename).toLowerCase().slice(1);
  for (const [format, extensions] of Object.entries(FORMAT_EXTENSIONS)) {
    if ((extensions as readonly string[]).includes(ext)) {
      return format as FileFormat;
    }
  }
  return 'json';
};

const getDefaultOutputFilename = (inputFile: string, format: FileFormat): string => {
  const parsedPath = path.parse(inputFile);
  const extension = format === 'yaml' ? '.yaml' : `.${format}`;
  return path.join(parsedPath.dir, `${parsedPath.name}${extension}`);
};

const extractCliOptions = (args: string[]): CliOptions => {
  const inputFile = args[0];
  const formatIndex = args.indexOf('--format');
  const formatValue = formatIndex !== -1 ? args[formatIndex + 1] : detectFileFormat(inputFile);
  
  const convertToIndex = args.indexOf('--convert-to');
  const convertToValue = convertToIndex !== -1 ? args[convertToIndex + 1] : null;

  if (formatValue && !VALID_FORMATS.includes(formatValue as FileFormat)) {
    console.error(chalk.red(`Error: Invalid format "${formatValue}". Valid formats are: ${VALID_FORMATS.join(', ')}`));
    process.exit(1);
  }

  if (convertToValue && !VALID_FORMATS.includes(convertToValue as FileFormat)) {
    console.error(chalk.red(`Error: Invalid conversion format "${convertToValue}". Valid formats are: ${VALID_FORMATS.join(', ')}`));
    process.exit(1);
  }

  const outputIndex = args.indexOf('--output');
  let outputFile = outputIndex !== -1 ? args[outputIndex + 1] : null;

  if (convertToValue && !outputFile) {
    outputFile = getDefaultOutputFilename(inputFile, convertToValue as FileFormat);
  }

  return {
    inputFile,
    outputFile,
    compareMode: args.includes('--compare'),
    safeMode: args.includes('--safe'),
    format: formatValue as FileFormat,
    convertTo: convertToValue as FileFormat | null,
    delimiter: args.indexOf('--delimiter') !== -1 ? args[args.indexOf('--delimiter') + 1] : ',',
    headers: !args.includes('--no-headers'),
    removeZeroValues: args.includes('--remove-zero-values'),
    noClean: args.includes('--noclean')
  };
};

const parseContent = (content: string, format: FileFormat, options: ParserOptions): AnyObject | AnyObject[] => {
  try {
    switch (format) {
      case 'yaml':
        return parseYAML(content);
      case 'csv':
        return parseCSV(content, { delimiter: options.delimiter || ',', headers: options.headers !== false });
      default:
        return JSON.parse(content);
    }
  } catch (error: any) {
    console.error(chalk.red(`Error parsing ${format.toUpperCase()} content: ${error.message}`));
    process.exit(1);
  }
};

const stringifyContent = (data: AnyObject | AnyObject[], format: FileFormat, options: ParserOptions): string => {
  try {
    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);
    }
  } catch (error: any) {
    console.error(chalk.red(`Error converting to ${format.toUpperCase()}: ${error.message}`));
    process.exit(1);
  }
};

const generateFieldMap = (original: any, cleaned: any, prefix = ''): string[] => (
  Object.entries(original).reduce((acc: string[], [key, value]) => {
    const currentPath = prefix ? `${prefix}.${key}` : key;
    
    if (!(key in cleaned)) {
      return [...acc, currentPath];
    }
    
    if (typeof value === 'object' && value !== null && typeof cleaned[key] === 'object' && cleaned[key] !== null) {
      return [...acc, ...generateFieldMap(value, cleaned[key], currentPath)];
    }
    
    return acc;
  }, [])
);

const executeCliOperation = (options: CliOptions): void => {
  const { inputFile, outputFile, compareMode, safeMode, format, convertTo, delimiter, headers, removeZeroValues, noClean } = options;
  
  if (!inputFile) {
    console.error(chalk.red('Please provide an input file'));
    console.log(chalk.yellow(
      'Usage: npx purify-objects input.file [--output cleaned.file] [--compare] [--safe] ' +
      `[--format ${VALID_FORMATS.join('|')}] [--convert-to ${VALID_FORMATS.join('|')}] ` +
      '[--delimiter ","] [--no-headers] [--remove-zero-values] [--noclean]'
    ));
    process.exit(1);
  }
  
  try {
    const inputPath = path.resolve(process.cwd(), inputFile);
    if (!fs.existsSync(inputPath)) {
      console.error(chalk.red(`Error: File not found: ${inputFile}`));
      process.exit(1);
    }

    const content = fs.readFileSync(inputPath, 'utf8');
    const sourceData = parseContent(content, format, { delimiter, headers });
    const customCleaner = removeZeroValues ? (key: string, value: any) => value === 0 : undefined;
    const processedData = noClean ? sourceData : Array.isArray(sourceData)
      ? sourceData.map(item => cleanObject(item, customCleaner, [], { safe: safeMode }))
      : cleanObject(sourceData, customCleaner, [], { safe: safeMode });
    
    if (compareMode) {
      safeMode && console.log(chalk.blue('\nSafe Mode: Original file will not be modified'));
      console.log(chalk.yellow('\nOriginal data:'), stringifyContent(sourceData, format, { delimiter, headers }));
      console.log(chalk.green('\nCleaned data (preview):'), stringifyContent(processedData, format, { delimiter, headers }));
      
      if (!Array.isArray(sourceData)) {
        const modifications = generateFieldMap(sourceData, processedData);
        if (modifications.length) {
          console.log(chalk.red('\nFields to be removed:'));
          modifications.forEach(f => console.log(chalk.red(`- ${f}`)));
        }
      }
      
      safeMode && console.log(chalk.blue('\nNo changes were made to the original file (Safe Mode)'));
      process.exit(0);
    }
    
    if (outputFile) {
      const outputPath = path.resolve(process.cwd(), outputFile);
      const outputFormat = convertTo || format;
      fs.writeFileSync(outputPath, stringifyContent(processedData, outputFormat, { delimiter, headers }));
      
      if (convertTo) {
        console.log(chalk.green(`\nFile successfully ${noClean ? 'converted' : 'converted and cleaned'}:`));
        console.log(chalk.blue(`Input: ${inputFile} (${format.toUpperCase()})`));
        console.log(chalk.blue(`Output: ${outputFile} (${convertTo.toUpperCase()})`));
      } else {
        safeMode && console.log(chalk.blue('Safe Mode: Created new file without modifying original'));
        console.log(chalk.green(`${noClean ? 'Data' : 'Cleaned data'} saved to ${outputFile}`));
      }
      return;
    }
    
    console.log(stringifyContent(processedData, format, { delimiter, headers }));
  } catch (error: any) {
    console.error(chalk.red('Error:'), error?.message || 'Unknown error occurred');
    process.exit(1);
  }
};

executeCliOperation(extractCliOptions(process.argv.slice(2))); 