package main

import (
	"bufio"
	"fmt"
	"io"
	"log"
	"os"
	"path/filepath"
	"strings"

	"valhallir-deconvolver/pkg/convolve"
	"valhallir-deconvolver/pkg/plot"
	"valhallir-deconvolver/pkg/wav"

	"github.com/urfave/cli/v2"
)

// processIR processes a single recorded file and generates IR(s)
func processIR(c *cli.Context, sweepData *wav.WAVData, recordedPath, outputPath string) error {
	// Read recorded WAV file
	recordedData, err := wav.ReadWAVFile(recordedPath)
	if err != nil {
		return fmt.Errorf("failed to read recorded file %s: %v", recordedPath, err)
	}

	log.Printf("Processing: %s", filepath.Base(recordedPath))
	log.Printf("Sweep: %d samples, %d channels", len(sweepData.PCMData), sweepData.Channels)
	log.Printf("Recorded: %d samples, %d channels", len(recordedData.PCMData), recordedData.Channels)

	// Optionally filter the recorded sweep
	recordedFiltered := recordedData.PCMData
	recSampleRate := recordedData.SampleRate
	highcutHz := c.Float64("highcut")
	lowcutHz := c.Float64("lowcut")
	cutSlope := c.Int("cut-slope")
	if cutSlope < 12 || cutSlope%12 != 0 {
		return fmt.Errorf("cut-slope must be a positive multiple of 12 (got %d)", cutSlope)
	}
	if lowcutHz > 0 {
		log.Printf("Applying low-cut (high-pass) filter to recorded sweep: %.2f Hz, slope: %d dB/oct", lowcutHz, cutSlope)
		recordedFiltered = convolve.CascadeLowcut(recordedFiltered, recSampleRate, lowcutHz, cutSlope)
	}
	if highcutHz > 0 {
		log.Printf("Applying high-cut (low-pass) filter to recorded sweep: %.2f Hz, slope: %d dB/oct", highcutHz, cutSlope)
		recordedFiltered = convolve.CascadeHighcut(recordedFiltered, recSampleRate, highcutHz, cutSlope)
	}

	// Force phase inversion if requested
	if c.Bool("force-invert-phase") {
		log.Printf("Forcing phase inversion of recorded sweep (manual override)")
		recordedFiltered = convolve.InvertPhase(recordedFiltered)
	}

	log.Println("Performing deconvolution...")
	var ir []float64
	if c.Bool("no-phase-correction") {
		ir = convolve.Deconvolve(sweepData.PCMData, recordedFiltered)
	} else {
		ir = convolve.DeconvolveWithPhaseCorrection(sweepData.PCMData, recordedFiltered)
	}
	log.Printf("Deconvolution result: %d samples", len(ir))

	log.Println("Trimming silence...")
	ir = convolve.TrimSilence(ir, 1e-5)
	log.Printf("After trimming: %d samples", len(ir))

	log.Println("Normalizing...")
	ir = convolve.Normalize(ir, c.Float64("normalize"))
	log.Printf("Final IR: %d samples", len(ir))

	// Validate output format options
	sampleRate := c.Int("sample-rate")
	bitDepth := c.Int("bit-depth")

	// Validate sample rate
	validSampleRates := []int{44100, 48000, 88200, 96000}
	validSampleRate := false
	for _, sr := range validSampleRates {
		if sampleRate == sr {
			validSampleRate = true
			break
		}
	}
	if !validSampleRate {
		return fmt.Errorf("invalid sample rate: %d. Valid options: %v", sampleRate, validSampleRates)
	}

	// Validate bit depth
	validBitDepths := []int{16, 24, 32}
	validBitDepth := false
	for _, bd := range validBitDepths {
		if bitDepth == bd {
			validBitDepth = true
			break
		}
	}
	if !validBitDepth {
		return fmt.Errorf("invalid bit depth: %d. Valid options: %v", bitDepth, validBitDepths)
	}

	// Resample IR to target sample rate if different from input (96kHz)
	targetSampleRate := sampleRate
	if targetSampleRate != 96000 {
		log.Printf("Resampling IR from 96kHz to %dHz...", targetSampleRate)
		ir = convolve.Resample(ir, 96000, targetSampleRate)
		log.Printf("Resampled IR: %d samples", len(ir))
	}

	// Trim or pad IR to requested length if --length-ms is set
	lengthMs := c.Float64("length-ms")
	if lengthMs > 0 {
		targetSamples := int(float64(targetSampleRate) * lengthMs / 1000.0)
		log.Printf("Trimming or padding IR to %d samples (%.2f ms)...", targetSamples, lengthMs)
		ir = convolve.TrimOrPad(ir, targetSamples)
	}

	// Apply fade-out
	fadeMs := c.Float64("fade-ms")
	fadeSamples := int(float64(targetSampleRate) * fadeMs / 1000.0)
	if fadeSamples > 0 {
		log.Printf("Applying linear fade-out: %d samples (%.2f ms)...", fadeSamples, fadeMs)
		ir = convolve.FadeOutLinear(ir, fadeSamples)
	}

	// Write regular IR
	log.Printf("Writing IR to: %s (%dHz, %d-bit WAV)", outputPath, sampleRate, bitDepth)
	if err := wav.WriteWAVFileWithOptions(outputPath, ir, sampleRate, bitDepth); err != nil {
		return fmt.Errorf("failed to write IR file: %v", err)
	}

	// Plot IR waveform if requested
	if c.Bool("plot-ir") {
		log.Printf("Plotting IR waveform...")
		err := plot.PlotIR(ir, sampleRate, outputPath)
		if err != nil {
			return fmt.Errorf("failed to plot IR: %v", err)
		}
		log.Printf("IR plot saved")
	}

	// Generate MPT IR if requested
	if c.Bool("mpt") {
		log.Println("Generating minimum phase transform...")
		// Use the original 96kHz IR for MPT generation
		var originalIR []float64
		if c.Bool("no-phase-correction") {
			originalIR = convolve.Deconvolve(sweepData.PCMData, recordedData.PCMData)
		} else {
			originalIR = convolve.DeconvolveWithPhaseCorrection(sweepData.PCMData, recordedData.PCMData)
		}
		originalIR = convolve.TrimSilence(originalIR, 1e-5)
		mptIR := convolve.MinimumPhaseTransform(originalIR)
		mptIR = convolve.Normalize(mptIR, c.Float64("normalize"))
		log.Printf("MPT IR: %d samples", len(mptIR))

		// Resample MPT IR to target sample rate if different from input (96kHz)
		if targetSampleRate != 96000 {
			log.Printf("Resampling MPT IR from 96kHz to %dHz...", targetSampleRate)
			mptIR = convolve.Resample(mptIR, 96000, targetSampleRate)
			log.Printf("Resampled MPT IR: %d samples", len(mptIR))
		}

		// Trim or pad MPT IR to requested length if --length-ms is set
		if lengthMs > 0 {
			targetSamples := int(float64(targetSampleRate) * lengthMs / 1000.0)
			log.Printf("Trimming or padding MPT IR to %d samples (%.2f ms)...", targetSamples, lengthMs)
			mptIR = convolve.TrimOrPad(mptIR, targetSamples)
		}

		// Apply fade-out to MPT IR
		if fadeSamples > 0 {
			log.Printf("Applying linear fade-out to MPT IR: %d samples (%.2f ms)...", fadeSamples, fadeMs)
			mptIR = convolve.FadeOutLinear(mptIR, fadeSamples)
		}

		// Generate MPT output filename
		mptOutputPath := outputPath
		if len(mptOutputPath) > 4 && mptOutputPath[len(mptOutputPath)-4:] == ".wav" {
			mptOutputPath = mptOutputPath[:len(mptOutputPath)-4]
		}
		mptOutputPath = mptOutputPath + "_mpt.wav"

		log.Printf("Writing MPT IR to: %s (%dHz, %d-bit WAV)", mptOutputPath, sampleRate, bitDepth)
		if err := wav.WriteWAVFileWithOptions(mptOutputPath, mptIR, sampleRate, bitDepth); err != nil {
			return fmt.Errorf("failed to write MPT IR file: %v", err)
		}
		log.Println("Minimum phase transform IR generated successfully!")
	}

	return nil
}

// processSingleFile processes a single recorded file
func processSingleFile(c *cli.Context, sweepData *wav.WAVData, recordedPath, outputPath string) error {
	return processIR(c, sweepData, recordedPath, outputPath)
}

// processDirectory processes all WAV files in a directory
func processDirectory(c *cli.Context, sweepData *wav.WAVData, recordedDir, outputDir string) error {
	// Check if output directory exists, create if not
	outputInfo, err := os.Stat(outputDir)
	if err != nil {
		if os.IsNotExist(err) {
			log.Printf("Creating output directory: %s", outputDir)
			if err := os.MkdirAll(outputDir, 0755); err != nil {
				return fmt.Errorf("failed to create output directory: %v", err)
			}
		} else {
			return fmt.Errorf("failed to access output directory: %v", err)
		}
	} else if !outputInfo.IsDir() {
		return fmt.Errorf("output path is not a directory: %s", outputDir)
	}

	// Find all WAV files in the recorded directory
	var wavFiles []string
	err = filepath.Walk(recordedDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() && strings.ToLower(filepath.Ext(path)) == ".wav" {
			wavFiles = append(wavFiles, path)
		}
		return nil
	})
	if err != nil {
		return fmt.Errorf("failed to scan recorded directory: %v", err)
	}

	if len(wavFiles) == 0 {
		return fmt.Errorf("no WAV files found in directory: %s", recordedDir)
	}

	log.Printf("Found %d WAV file(s) to process", len(wavFiles))

	// Process each WAV file
	successCount := 0
	for i, recordedFile := range wavFiles {
		log.Printf("\n[%d/%d] Processing: %s", i+1, len(wavFiles), filepath.Base(recordedFile))

		// Generate output filename based on recorded filename
		recordedBase := filepath.Base(recordedFile)
		recordedName := strings.TrimSuffix(recordedBase, filepath.Ext(recordedBase))
		outputFile := filepath.Join(outputDir, recordedName+".wav")

		if err := processIR(c, sweepData, recordedFile, outputFile); err != nil {
			log.Printf("ERROR processing %s: %v", recordedFile, err)
			continue
		}

		successCount++
		log.Printf("Successfully processed: %s -> %s", recordedBase, filepath.Base(outputFile))
	}

	log.Printf("\nBatch processing complete: %d/%d files processed successfully", successCount, len(wavFiles))
	if successCount < len(wavFiles) {
		return fmt.Errorf("some files failed to process (%d/%d succeeded)", successCount, len(wavFiles))
	}

	return nil
}

// extractCabpackBaseName extracts the cabpack base name from a WAV filename
// Example: "V2-1960STV-d-SM7B-A1.wav" -> "V2-1960STV"
func extractCabpackBaseName(filename string) string {
	base := filepath.Base(filename)
	name := strings.TrimSuffix(base, filepath.Ext(base))

	// Find the first occurrence of a pattern like "-d-", "-SM7B-", etc.
	// We look for the pattern where the first part (before the first dash after the base) is the cabpack name
	parts := strings.Split(name, "-")
	if len(parts) >= 2 {
		// Take the first two parts (e.g., "V2" and "1960STV")
		return strings.Join(parts[:2], "-")
	}
	// Fallback: return the first part or the whole name
	if len(parts) > 0 {
		return parts[0]
	}
	return name
}

// formatSpec represents a format specification for cabpack generation
type formatSpec struct {
	SampleRate int
	BitDepth   int
	LengthMs   float64
}

// getCabpackFormats returns all format specifications for cabpack generation
func getCabpackFormats() []formatSpec {
	return []formatSpec{
		{44100, 16, 170},
		{44100, 24, 170},
		{44100, 24, 500},
		{48000, 16, 170},
		{48000, 24, 170},
		{48000, 24, 500},
		{48000, 24, 1370},
		{96000, 24, 500},
		{96000, 24, 1370},
	}
}

// formatFolderName generates the format folder name
func formatFolderName(cabpackBase string, spec formatSpec) string {
	return fmt.Sprintf("%s %dHz-%dbit %.0fms", cabpackBase, spec.SampleRate, spec.BitDepth, spec.LengthMs)
}

// processCabpack processes all WAV files in a directory and creates a cabpack structure
func processCabpack(c *cli.Context, sweepData *wav.WAVData, recordedDir, outputDir, sweepFileName string) error {
	// Apply cabpack defaults if not explicitly set
	// fade-ms: default 10 for cabpack (vs 5 normally)
	fadeMs := c.Float64("fade-ms")
	if !c.IsSet("fade-ms") {
		fadeMs = 10.0
		log.Printf("Using cabpack default: fade-ms = 10")
	}

	// lowcut: default 40 for cabpack
	lowcutHz := c.Float64("lowcut")
	if !c.IsSet("lowcut") {
		lowcutHz = 40.0
		log.Printf("Using cabpack default: lowcut = 40 Hz")
	}

	// cut-slope: default is already 12, so no change needed
	cutSlope := c.Int("cut-slope")

	// Find all WAV files in the recorded directory, excluding the sweep file
	var wavFiles []string
	err := filepath.Walk(recordedDir, func(path string, info os.FileInfo, err error) error {
		if err != nil {
			return err
		}
		if !info.IsDir() && strings.ToLower(filepath.Ext(path)) == ".wav" {
			// Exclude the sweep file from processing
			fileName := filepath.Base(path)
			if fileName != sweepFileName {
				wavFiles = append(wavFiles, path)
			}
		}
		return nil
	})
	if err != nil {
		return fmt.Errorf("failed to scan recorded directory: %v", err)
	}

	if len(wavFiles) == 0 {
		return fmt.Errorf("no WAV files found in directory: %s", recordedDir)
	}

	log.Printf("Found %d WAV file(s) to process for cabpack", len(wavFiles))

	// Extract cabpack base name from the first file
	firstFile := filepath.Base(wavFiles[0])
	cabpackBase := extractCabpackBaseName(firstFile)
	log.Printf("Cabpack base name: %s", cabpackBase)

	// Verify all files have the same base name
	for _, wavFile := range wavFiles {
		base := extractCabpackBaseName(filepath.Base(wavFile))
		if base != cabpackBase {
			log.Printf("Warning: File %s has different base name (%s), expected %s", wavFile, base, cabpackBase)
		}
	}

	// Create output directory if it doesn't exist
	if err := os.MkdirAll(outputDir, 0755); err != nil {
		return fmt.Errorf("failed to create output directory: %v", err)
	}

	// Get all format specifications
	formats := getCabpackFormats()

	// Create directory structure for each format
	for _, format := range formats {
		formatFolder := formatFolderName(cabpackBase, format)
		formatPath := filepath.Join(outputDir, formatFolder)

		// Create format folder
		if err := os.MkdirAll(formatPath, 0755); err != nil {
			return fmt.Errorf("failed to create format folder %s: %v", formatPath, err)
		}

		// Create subfolders: ambient mics, close mics, mixees, selection
		subfolders := []string{"ambient mics", "close mics", "mixees", "selection"}
		for _, subfolder := range subfolders {
			subfolderPath := filepath.Join(formatPath, subfolder)
			if err := os.MkdirAll(subfolderPath, 0755); err != nil {
				return fmt.Errorf("failed to create subfolder %s: %v", subfolderPath, err)
			}
		}

		// Create RAW and MPT folders inside "close mics"
		closeMicsPath := filepath.Join(formatPath, "close mics")
		rawPath := filepath.Join(closeMicsPath, "RAW")
		mptPath := filepath.Join(closeMicsPath, "MPT")
		if err := os.MkdirAll(rawPath, 0755); err != nil {
			return fmt.Errorf("failed to create RAW folder: %v", err)
		}
		if err := os.MkdirAll(mptPath, 0755); err != nil {
			return fmt.Errorf("failed to create MPT folder: %v", err)
		}

		// Create RAW and MPT folders inside "selection"
		selectionPath := filepath.Join(formatPath, "selection")
		selectionRawPath := filepath.Join(selectionPath, "RAW")
		selectionMptPath := filepath.Join(selectionPath, "MPT")
		if err := os.MkdirAll(selectionRawPath, 0755); err != nil {
			return fmt.Errorf("failed to create selection RAW folder: %v", err)
		}
		if err := os.MkdirAll(selectionMptPath, 0755); err != nil {
			return fmt.Errorf("failed to create selection MPT folder: %v", err)
		}

		// Create RAW and MPT folders inside "ambient mics"
		ambientPath := filepath.Join(formatPath, "ambient mics")
		ambientRawPath := filepath.Join(ambientPath, "RAW")
		ambientMptPath := filepath.Join(ambientPath, "MPT")
		if err := os.MkdirAll(ambientRawPath, 0755); err != nil {
			return fmt.Errorf("failed to create ambient RAW folder: %v", err)
		}
		if err := os.MkdirAll(ambientMptPath, 0755); err != nil {
			return fmt.Errorf("failed to create ambient MPT folder: %v", err)
		}
	}

	// Create plots folder at cabpack level
	plotsPath := filepath.Join(outputDir, "plots")
	if err := os.MkdirAll(plotsPath, 0755); err != nil {
		return fmt.Errorf("failed to create plots folder: %v", err)
	}

	// Process each WAV file
	successCount := 0
	for i, recordedFile := range wavFiles {
		log.Printf("\n[%d/%d] Processing: %s", i+1, len(wavFiles), filepath.Base(recordedFile))

		// Read recorded WAV file
		recordedData, err := wav.ReadWAVFile(recordedFile)
		if err != nil {
			log.Printf("ERROR reading %s: %v", recordedFile, err)
			continue
		}

		// Get base name for output files (without extension)
		recordedBase := filepath.Base(recordedFile)
		recordedName := strings.TrimSuffix(recordedBase, filepath.Ext(recordedBase))

		// Process each format
		for _, format := range formats {
			log.Printf("  Processing format: %dHz, %d-bit, %.0fms", format.SampleRate, format.BitDepth, format.LengthMs)

			// Process IR for this format
			formatFolder := formatFolderName(cabpackBase, format)
			rawPath := filepath.Join(outputDir, formatFolder, "close mics", "RAW")
			mptPath := filepath.Join(outputDir, formatFolder, "close mics", "MPT")

			// Generate RAW IR
			rawOutputPath := filepath.Join(rawPath, recordedName+".wav")
			if err := processIRForFormatWithDefaults(c, sweepData, recordedData, rawOutputPath, format, false, fadeMs, lowcutHz, cutSlope); err != nil {
				log.Printf("ERROR generating RAW IR for %s in format %s: %v", recordedName, formatFolder, err)
				continue
			}

			// Generate MPT IR
			mptOutputPath := filepath.Join(mptPath, recordedName+".wav")
			if err := processIRForFormatWithDefaults(c, sweepData, recordedData, mptOutputPath, format, true, fadeMs, lowcutHz, cutSlope); err != nil {
				log.Printf("ERROR generating MPT IR for %s in format %s: %v", recordedName, formatFolder, err)
				continue
			}
		}

		// Generate plot for 96000Hz format (only once per file)
		plotFormat := formatSpec{96000, 24, 500}
		plotIR, err := generateIRForFormatWithDefaults(c, sweepData, recordedData, plotFormat, false, fadeMs, lowcutHz, cutSlope)
		if err != nil {
			log.Printf("ERROR generating IR for plot: %v", err)
		} else {
			// Pass path without .png extension - PlotIR will add it
			plotBasePath := filepath.Join(plotsPath, recordedName)
			if err := plot.PlotIR(plotIR, plotFormat.SampleRate, plotBasePath+".wav"); err != nil {
				log.Printf("ERROR generating plot: %v", err)
			} else {
				log.Printf("Plot saved: %s.png", plotBasePath)
			}
		}

		successCount++
		log.Printf("Successfully processed: %s", recordedBase)
	}

	log.Printf("\nCabpack generation complete: %d/%d files processed successfully", successCount, len(wavFiles))
	if successCount < len(wavFiles) {
		return fmt.Errorf("some files failed to process (%d/%d succeeded)", successCount, len(wavFiles))
	}

	// Process selection.txt and ambient.txt files
	log.Printf("\nProcessing selection and ambient file lists...")
	if err := processSelectionAndAmbientLists(recordedDir, outputDir, cabpackBase, formats); err != nil {
		log.Printf("Warning: Error processing selection/ambient lists: %v", err)
		// Don't fail the entire process if this fails
	}

	return nil
}

// readFileList reads a text file with one filename per line and returns the list
func readFileList(filePath string) ([]string, error) {
	file, err := os.Open(filePath)
	if err != nil {
		return nil, err
	}
	defer file.Close()

	var files []string
	scanner := bufio.NewScanner(file)
	for scanner.Scan() {
		line := strings.TrimSpace(scanner.Text())
		if line != "" {
			files = append(files, line)
		}
	}

	if err := scanner.Err(); err != nil {
		return nil, err
	}

	return files, nil
}

// copyFile copies a file from source to destination
func copyFile(src, dst string) error {
	sourceFile, err := os.Open(src)
	if err != nil {
		return err
	}
	defer sourceFile.Close()

	destFile, err := os.Create(dst)
	if err != nil {
		return err
	}
	defer destFile.Close()

	_, err = io.Copy(destFile, sourceFile)
	if err != nil {
		return err
	}

	return destFile.Sync()
}

// moveFile moves a file from source to destination
func moveFile(src, dst string) error {
	if err := copyFile(src, dst); err != nil {
		return err
	}
	return os.Remove(src)
}

// processSelectionAndAmbientLists processes selection.txt and ambient.txt files
func processSelectionAndAmbientLists(recordedDir, outputDir, cabpackBase string, formats []formatSpec) error {
	// Process selection.txt first (copy files)
	selectionListPath := filepath.Join(recordedDir, "selection.txt")
	if _, err := os.Stat(selectionListPath); err == nil {
		log.Printf("Processing selection.txt...")
		selectionFiles, err := readFileList(selectionListPath)
		if err != nil {
			return fmt.Errorf("failed to read selection.txt: %v", err)
		}

		log.Printf("Found %d files in selection.txt", len(selectionFiles))

		// Process each format
		for _, format := range formats {
			formatFolder := formatFolderName(cabpackBase, format)
			formatPath := filepath.Join(outputDir, formatFolder)

			closeMicsRawPath := filepath.Join(formatPath, "close mics", "RAW")
			closeMicsMptPath := filepath.Join(formatPath, "close mics", "MPT")
			selectionRawPath := filepath.Join(formatPath, "selection", "RAW")
			selectionMptPath := filepath.Join(formatPath, "selection", "MPT")

			// Copy each file from close mics to selection
			for _, fileName := range selectionFiles {
				// Ensure filename has .wav extension
				if !strings.HasSuffix(strings.ToLower(fileName), ".wav") {
					fileName = fileName + ".wav"
				}

				// Copy RAW file
				srcRaw := filepath.Join(closeMicsRawPath, fileName)
				dstRaw := filepath.Join(selectionRawPath, fileName)
				if _, err := os.Stat(srcRaw); err == nil {
					if err := copyFile(srcRaw, dstRaw); err != nil {
						log.Printf("Warning: Failed to copy RAW file %s: %v", fileName, err)
					} else {
						log.Printf("  Copied RAW: %s", fileName)
					}
				} else {
					log.Printf("Warning: RAW file not found: %s", srcRaw)
				}

				// Copy MPT file
				srcMpt := filepath.Join(closeMicsMptPath, fileName)
				dstMpt := filepath.Join(selectionMptPath, fileName)
				if _, err := os.Stat(srcMpt); err == nil {
					if err := copyFile(srcMpt, dstMpt); err != nil {
						log.Printf("Warning: Failed to copy MPT file %s: %v", fileName, err)
					} else {
						log.Printf("  Copied MPT: %s", fileName)
					}
				} else {
					log.Printf("Warning: MPT file not found: %s", srcMpt)
				}
			}
		}
		log.Printf("Selection processing complete")
	} else {
		log.Printf("selection.txt not found, skipping selection processing")
	}

	// Process ambient.txt (move files)
	ambientListPath := filepath.Join(recordedDir, "ambient.txt")
	if _, err := os.Stat(ambientListPath); err == nil {
		log.Printf("Processing ambient.txt...")
		ambientFiles, err := readFileList(ambientListPath)
		if err != nil {
			return fmt.Errorf("failed to read ambient.txt: %v", err)
		}

		log.Printf("Found %d files in ambient.txt", len(ambientFiles))

		// Process each format
		for _, format := range formats {
			formatFolder := formatFolderName(cabpackBase, format)
			formatPath := filepath.Join(outputDir, formatFolder)

			closeMicsRawPath := filepath.Join(formatPath, "close mics", "RAW")
			closeMicsMptPath := filepath.Join(formatPath, "close mics", "MPT")
			ambientRawPath := filepath.Join(formatPath, "ambient mics", "RAW")
			ambientMptPath := filepath.Join(formatPath, "ambient mics", "MPT")

			// Move each file from close mics to ambient mics
			for _, fileName := range ambientFiles {
				// Ensure filename has .wav extension
				if !strings.HasSuffix(strings.ToLower(fileName), ".wav") {
					fileName = fileName + ".wav"
				}

				// Move RAW file
				srcRaw := filepath.Join(closeMicsRawPath, fileName)
				dstRaw := filepath.Join(ambientRawPath, fileName)
				if _, err := os.Stat(srcRaw); err == nil {
					if err := moveFile(srcRaw, dstRaw); err != nil {
						log.Printf("Warning: Failed to move RAW file %s: %v", fileName, err)
					} else {
						log.Printf("  Moved RAW: %s", fileName)
					}
				} else {
					log.Printf("Warning: RAW file not found: %s", srcRaw)
				}

				// Move MPT file
				srcMpt := filepath.Join(closeMicsMptPath, fileName)
				dstMpt := filepath.Join(ambientMptPath, fileName)
				if _, err := os.Stat(srcMpt); err == nil {
					if err := moveFile(srcMpt, dstMpt); err != nil {
						log.Printf("Warning: Failed to move MPT file %s: %v", fileName, err)
					} else {
						log.Printf("  Moved MPT: %s", fileName)
					}
				} else {
					log.Printf("Warning: MPT file not found: %s", srcMpt)
				}
			}
		}
		log.Printf("Ambient processing complete")
	} else {
		log.Printf("ambient.txt not found, skipping ambient processing")
	}

	return nil
}

// processIRForFormatWithDefaults processes an IR with explicit defaults for cabpack mode
func processIRForFormatWithDefaults(c *cli.Context, sweepData *wav.WAVData, recordedData *wav.WAVData, outputPath string, format formatSpec, generateMPT bool, fadeMs, lowcutHz float64, cutSlope int) error {
	// Optionally filter the recorded sweep
	recordedFiltered := recordedData.PCMData
	recSampleRate := recordedData.SampleRate
	highcutHz := c.Float64("highcut")
	if cutSlope < 12 || cutSlope%12 != 0 {
		return fmt.Errorf("cut-slope must be a positive multiple of 12 (got %d)", cutSlope)
	}
	if lowcutHz > 0 {
		recordedFiltered = convolve.CascadeLowcut(recordedFiltered, recSampleRate, lowcutHz, cutSlope)
	}
	if highcutHz > 0 {
		recordedFiltered = convolve.CascadeHighcut(recordedFiltered, recSampleRate, highcutHz, cutSlope)
	}

	// Force phase inversion if requested
	if c.Bool("force-invert-phase") {
		recordedFiltered = convolve.InvertPhase(recordedFiltered)
	}

	var ir []float64
	if c.Bool("no-phase-correction") {
		ir = convolve.Deconvolve(sweepData.PCMData, recordedFiltered)
	} else {
		ir = convolve.DeconvolveWithPhaseCorrection(sweepData.PCMData, recordedFiltered)
	}

	ir = convolve.TrimSilence(ir, 1e-5)
	ir = convolve.Normalize(ir, c.Float64("normalize"))

	// Resample IR to target sample rate if different from input (96kHz)
	targetSampleRate := format.SampleRate
	if targetSampleRate != 96000 {
		ir = convolve.Resample(ir, 96000, targetSampleRate)
	}

	// Trim or pad IR to requested length
	targetSamples := int(float64(targetSampleRate) * format.LengthMs / 1000.0)
	ir = convolve.TrimOrPad(ir, targetSamples)

	// Apply fade-out (using provided fadeMs parameter)
	fadeSamples := int(float64(targetSampleRate) * fadeMs / 1000.0)
	if fadeSamples > 0 {
		ir = convolve.FadeOutLinear(ir, fadeSamples)
	}

	// Generate MPT if requested
	if generateMPT {
		// Use the original 96kHz IR for MPT generation
		var originalIR []float64
		if c.Bool("no-phase-correction") {
			originalIR = convolve.Deconvolve(sweepData.PCMData, recordedData.PCMData)
		} else {
			originalIR = convolve.DeconvolveWithPhaseCorrection(sweepData.PCMData, recordedData.PCMData)
		}
		originalIR = convolve.TrimSilence(originalIR, 1e-5)
		mptIR := convolve.MinimumPhaseTransform(originalIR)
		mptIR = convolve.Normalize(mptIR, c.Float64("normalize"))

		// Resample MPT IR to target sample rate if different from input (96kHz)
		if targetSampleRate != 96000 {
			mptIR = convolve.Resample(mptIR, 96000, targetSampleRate)
		}

		// Trim or pad MPT IR to requested length
		mptIR = convolve.TrimOrPad(mptIR, targetSamples)

		// Apply fade-out to MPT IR
		if fadeSamples > 0 {
			mptIR = convolve.FadeOutLinear(mptIR, fadeSamples)
		}

		ir = mptIR
	}

	// Write IR
	if err := wav.WriteWAVFileWithOptions(outputPath, ir, format.SampleRate, format.BitDepth); err != nil {
		return fmt.Errorf("failed to write IR file: %v", err)
	}

	return nil
}

// generateIRForFormatWithDefaults generates an IR with explicit defaults for cabpack mode
func generateIRForFormatWithDefaults(c *cli.Context, sweepData *wav.WAVData, recordedData *wav.WAVData, format formatSpec, generateMPT bool, fadeMs, lowcutHz float64, cutSlope int) ([]float64, error) {
	// Optionally filter the recorded sweep
	recordedFiltered := recordedData.PCMData
	recSampleRate := recordedData.SampleRate
	highcutHz := c.Float64("highcut")
	if cutSlope < 12 || cutSlope%12 != 0 {
		return nil, fmt.Errorf("cut-slope must be a positive multiple of 12 (got %d)", cutSlope)
	}
	if lowcutHz > 0 {
		recordedFiltered = convolve.CascadeLowcut(recordedFiltered, recSampleRate, lowcutHz, cutSlope)
	}
	if highcutHz > 0 {
		recordedFiltered = convolve.CascadeHighcut(recordedFiltered, recSampleRate, highcutHz, cutSlope)
	}

	// Force phase inversion if requested
	if c.Bool("force-invert-phase") {
		recordedFiltered = convolve.InvertPhase(recordedFiltered)
	}

	var ir []float64
	if c.Bool("no-phase-correction") {
		ir = convolve.Deconvolve(sweepData.PCMData, recordedFiltered)
	} else {
		ir = convolve.DeconvolveWithPhaseCorrection(sweepData.PCMData, recordedFiltered)
	}

	ir = convolve.TrimSilence(ir, 1e-5)
	ir = convolve.Normalize(ir, c.Float64("normalize"))

	// Resample IR to target sample rate if different from input (96kHz)
	targetSampleRate := format.SampleRate
	if targetSampleRate != 96000 {
		ir = convolve.Resample(ir, 96000, targetSampleRate)
	}

	// Trim or pad IR to requested length
	targetSamples := int(float64(targetSampleRate) * format.LengthMs / 1000.0)
	ir = convolve.TrimOrPad(ir, targetSamples)

	// Apply fade-out (using provided fadeMs parameter)
	fadeSamples := int(float64(targetSampleRate) * fadeMs / 1000.0)
	if fadeSamples > 0 {
		ir = convolve.FadeOutLinear(ir, fadeSamples)
	}

	// Generate MPT if requested
	if generateMPT {
		// Use the original 96kHz IR for MPT generation
		var originalIR []float64
		if c.Bool("no-phase-correction") {
			originalIR = convolve.Deconvolve(sweepData.PCMData, recordedData.PCMData)
		} else {
			originalIR = convolve.DeconvolveWithPhaseCorrection(sweepData.PCMData, recordedData.PCMData)
		}
		originalIR = convolve.TrimSilence(originalIR, 1e-5)
		mptIR := convolve.MinimumPhaseTransform(originalIR)
		mptIR = convolve.Normalize(mptIR, c.Float64("normalize"))

		// Resample MPT IR to target sample rate if different from input (96kHz)
		if targetSampleRate != 96000 {
			mptIR = convolve.Resample(mptIR, 96000, targetSampleRate)
		}

		// Trim or pad MPT IR to requested length
		mptIR = convolve.TrimOrPad(mptIR, targetSamples)

		// Apply fade-out to MPT IR
		if fadeSamples > 0 {
			mptIR = convolve.FadeOutLinear(mptIR, fadeSamples)
		}

		ir = mptIR
	}

	return ir, nil
}

func main() {
	app := &cli.App{
		Name:    "valhallir-deconvolver",
		Usage:   "Deconvolve sweep and recorded WAV files to create impulse responses",
		Version: "v1.1.0",
		Flags: []cli.Flag{
			&cli.StringFlag{
				Name:  "sweep",
				Usage: "Path to the sweep WAV file (96kHz 24bit). Default: sweep.wav in current directory",
			},
			&cli.StringFlag{
				Name:  "recorded",
				Usage: "Path to the recorded WAV file or directory containing WAV files. Default: current directory",
			},
			&cli.StringFlag{
				Name:  "output",
				Usage: "Path to the output IR WAV file or directory for batch processing. Default: IRs subfolder in recorded directory",
			},
			&cli.BoolFlag{
				Name:  "mpt",
				Usage: "Generate minimum phase transform IR in addition to regular IR",
			},
			&cli.IntFlag{
				Name:  "sample-rate",
				Usage: "Output sample rate (44, 48, 88, 96 kHz)",
				Value: 96000,
			},
			&cli.IntFlag{
				Name:  "bit-depth",
				Usage: "Output bit depth (16, 24, 32 bit)",
				Value: 24,
			},
			&cli.Float64Flag{
				Name:  "normalize",
				Usage: "Normalize output to this peak value (0.0-1.0, default 0.95)",
				Value: 0.95,
			},
			&cli.Float64Flag{
				Name:  "trim-threshold",
				Usage: "Silence threshold for trimming (0.0-1.0, default 0.001)",
				Value: 0.001,
			},
			&cli.Float64Flag{
				Name:  "length-ms",
				Usage: "Optional: Output IR length in milliseconds (will trim or zero-pad as needed)",
			},
			&cli.Float64Flag{
				Name:  "fade-ms",
				Usage: "Fade-out duration in milliseconds to apply at the end of the IR (default 5)",
				Value: 5.0,
			},
			&cli.Float64Flag{
				Name:  "highcut",
				Usage: "High-cut filter (low-pass) cutoff frequency in Hz (applied to recorded sweep, optional)",
			},
			&cli.Float64Flag{
				Name:  "lowcut",
				Usage: "Low-cut filter (high-pass) cutoff frequency in Hz (applied to recorded sweep, optional)",
			},
			&cli.IntFlag{
				Name:  "cut-slope",
				Usage: "Cut filter slope in dB/octave (12, 24, 36, 48, ...; default 12)",
				Value: 12,
			},
			&cli.BoolFlag{
				Name:  "plot-ir",
				Usage: "Plot the generated regular IR waveform to ir_plot.png",
			},
			&cli.BoolFlag{
				Name:  "no-phase-correction",
				Usage: "Disable automatic phase correction (use if you know the phase is correct)",
			},
			&cli.BoolFlag{
				Name:  "force-invert-phase",
				Usage: "Force inversion of the recorded sweep (for testing/manual override)",
			},
			&cli.BoolFlag{
				Name:  "cabpack",
				Usage: "Generate a cabpack with IRs in multiple formats organized in a directory tree",
			},
		},
		Action: func(c *cli.Context) error {
			// Get the directory where the executable is located
			// This works when double-clicking the binary in Finder/Explorer
			execPath, err := os.Executable()
			if err != nil {
				return fmt.Errorf("failed to get executable path: %v", err)
			}
			execPath, err = filepath.EvalSymlinks(execPath)
			if err != nil {
				return fmt.Errorf("failed to resolve executable symlinks: %v", err)
			}
			execDir := filepath.Dir(execPath)
			execDir = filepath.Clean(execDir)

			// Get current working directory as fallback
			cwd, err := os.Getwd()
			if err != nil {
				cwd = execDir // Fallback to exec directory if Getwd fails
			}
			cwd = filepath.Clean(cwd)

			log.Printf("Executable directory: %s", execDir)
			log.Printf("Current working directory: %s", cwd)

			// Set defaults if flags are not provided
			sweepPath := c.String("sweep")
			recordedPath := c.String("recorded")
			outputPath := c.String("output")
			cabpackMode := c.Bool("cabpack")

			// If no flags provided, use default behavior (cabpack mode)
			// Use executable directory as default (works when double-clicking)
			if !c.IsSet("sweep") && !c.IsSet("recorded") && !c.IsSet("output") && !c.IsSet("cabpack") {
				log.Printf("No command-line options provided, using default cabpack mode")
				recordedPath = execDir
				sweepPath = filepath.Join(recordedPath, "sweep.wav")
				// Create IRs folder one directory level up
				outputPath = filepath.Join(filepath.Dir(recordedPath), "IRs")
				cabpackMode = true
				log.Printf("Using recorded directory: %s", recordedPath)
				log.Printf("Looking for sweep file: %s", sweepPath)
				log.Printf("Output directory: %s", outputPath)
			} else {
				// Set defaults for individual flags if not provided
				if !c.IsSet("recorded") {
					recordedPath = execDir
				}
				recordedPath = filepath.Clean(recordedPath)
				if !c.IsSet("sweep") {
					sweepPath = filepath.Join(recordedPath, "sweep.wav")
				}
				if !c.IsSet("output") {
					// If recorded is a directory, use IRs folder one level up, otherwise use parent directory
					recordedInfo, err := os.Stat(recordedPath)
					if err == nil && recordedInfo.IsDir() {
						outputPath = filepath.Join(filepath.Dir(recordedPath), "IRs")
					} else {
						outputPath = filepath.Dir(recordedPath)
					}
				}
			}

			// Read sweep WAV file
			sweepData, err := wav.ReadWAVFile(sweepPath)
			if err != nil {
				return fmt.Errorf("failed to read sweep file %s: %v", sweepPath, err)
			}

			// Check if cabpack mode is enabled
			if cabpackMode {
				log.Printf("Cabpack mode enabled")
				// Clean the path first to handle trailing slashes and normalize separators
				recordedPath = filepath.Clean(recordedPath)
				recordedInfo, err := os.Stat(recordedPath)
				if err != nil {
					return fmt.Errorf("failed to access recorded path %s: %v", recordedPath, err)
				}
				if !recordedInfo.IsDir() {
					return fmt.Errorf("cabpack mode requires --recorded to be a directory")
				}
				sweepFileName := filepath.Base(sweepPath)
				return processCabpack(c, sweepData, recordedPath, outputPath, sweepFileName)
			}

			// Check if recorded is a directory
			// Clean the path first to handle trailing slashes and normalize separators
			recordedPath = filepath.Clean(recordedPath)

			recordedInfo, err := os.Stat(recordedPath)
			if err != nil {
				return fmt.Errorf("failed to access recorded path %s: %v", recordedPath, err)
			}

			// Check if it's a directory (follow symlinks)
			if recordedInfo.IsDir() {
				// Batch processing mode
				log.Printf("Detected directory mode: processing all WAV files in %s", recordedPath)
				return processDirectory(c, sweepData, recordedPath, outputPath)
			} else {
				// Single file processing mode
				log.Printf("Detected single file mode: processing %s", recordedPath)
				return processSingleFile(c, sweepData, recordedPath, outputPath)
			}
		},
	}

	if err := app.Run(os.Args); err != nil {
		log.Fatal(err)
	}
}
