import path from "path";
import { FFmpegProcessManager } from "./ffmpeg-manager";
import {
  DisplayRecordingItem,
  RecorderOptions,
  RecordingMode,
  RecordingResult,
  WebcamRecordingItem,
} from "./types";
import {
  createOutputDirectory,
  getAbsolutePath,
  getDeviceIndex,
  getVideoMetadata,
  waitForFile,
} from "./utils";

export class Recorder {
  private options: RecorderOptions;
  private outputDirectory: string;
  private processManager: FFmpegProcessManager = new FFmpegProcessManager();
  private prepared: boolean = false;
  private recording: boolean = false;
  private recordingFiles: {
    path: string;
    type: RecordingMode;
    deviceName?: string;
  }[] = [];

  constructor(options: RecorderOptions) {
    this.options = options;
    this.outputDirectory = createOutputDirectory(options.output_directory);
  }

  /**
   * Prepare the recorder
   */
  public async prepare(): Promise<void> {
    if (this.prepared) {
      throw new Error("Recorder is already prepared");
    }

    // Validate that we have recording items
    if (!this.options.items || this.options.items.length === 0) {
      throw new Error("No recording items provided");
    }

    // Reset recording files
    this.recordingFiles = [];

    // Prepare output paths for each recording item
    for (const item of this.options.items) {
      const filename = `${item.type}-recording.mp4`;
      const deviceName =
        item.type === "display"
          ? item.display.name
          : item.type === "webcam"
            ? item.camera.name
            : undefined;

      this.recordingFiles.push({
        path: path.join(this.outputDirectory, filename),
        type: item.type,
        deviceName,
      });
    }

    this.prepared = true;
  }

  /**
   * Start recording
   */
  public async start(): Promise<void> {
    if (this.recording) {
      throw new Error("Recording is already in progress");
    }

    if (!this.prepared) {
      await this.prepare();
    }

    try {
      // Start recording for each item
      for (let i = 0; i < this.options.items.length; i++) {
        const item = this.options.items[i];
        if (!item) continue; // Skip if item is undefined

        const outputPath = this.recordingFiles[i]?.path;
        if (!outputPath) continue; // Skip if path is undefined

        if (item.type === "display") {
          await this.startDisplayRecording(item, outputPath);
        } else if (item.type === "webcam") {
          await this.startWebcamRecording(item, outputPath);
        } else {
          console.warn(
            `Skipping invalid recording item: ${JSON.stringify(item)}`
          );
        }
      }

      this.recording = true;
    } catch (error) {
      // If there's an error during start, make sure to clean up any started processes
      await this.processManager.stopAllProcesses();

      // Re-throw the error after cleanup
      throw error;
    }
  }

  /**
   * Stop recording and return result
   */
  public async stop(): Promise<RecordingResult> {
    if (!this.recording) {
      throw new Error("No recording in progress");
    }

    // Stop all processes
    await this.processManager.stopAllProcesses();

    // Wait for all recording files to be ready and collect metadata
    const result: RecordingResult = { files: [] };

    for (const file of this.recordingFiles) {
      const fileReady = await waitForFile(file.path);

      if (fileReady) {
        const metadata = await getVideoMetadata(file.path);

        if (metadata) {
          result.files.push({
            path: getAbsolutePath(file.path),
            type: file.type,
            deviceName: file.deviceName,
            dimensions: metadata.dimensions,
            durationInSeconds: metadata.durationInSeconds,
          });
        } else {
          result.files.push({
            path: getAbsolutePath(file.path),
            type: file.type,
            deviceName: file.deviceName,
          });
        }
      } else {
        console.warn(`File not ready after waiting: ${file.path}`);
      }
    }

    this.recording = false;
    this.prepared = false;

    return result;
  }

  /**
   * Start recording a display
   */
  private async startDisplayRecording(
    item: DisplayRecordingItem,
    outputPath: string
  ): Promise<void> {
    const processName = `display-${item.display.id}`;

    // Get display device index
    const displayIndex = await getDeviceIndex("display");

    if (displayIndex === null) {
      throw new Error("Could not find display device");
    }

    const ffmpegArgs: string[] = ["-f", "avfoundation", "-framerate", "60"];

    // Check if microphone is enabled
    if (item.microphone) {
      const microphoneIndex = await getDeviceIndex("microphone");

      if (microphoneIndex !== null) {
        ffmpegArgs.push("-i", `${displayIndex}:${microphoneIndex}`);
      } else {
        console.warn("Requested microphone not found, recording without audio");
        ffmpegArgs.push("-i", `${displayIndex}:`);
      }
    } else {
      ffmpegArgs.push("-i", `${displayIndex}:`);
    }

    // Add video settings
    ffmpegArgs.push(
      "-r",
      "60", // 60fps
      "-c:v",
      "libx264", // H.264 codec
      "-preset",
      "medium", // Balance quality and speed
      "-crf",
      "23", // Quality level
      "-profile:v",
      "high", // High profile for better quality
      "-pix_fmt",
      "yuv420p" // Standard pixel format
    );

    // Add audio settings if microphone is enabled
    if (item.microphone) {
      ffmpegArgs.push(
        "-c:a",
        "aac", // AAC audio codec
        "-b:a",
        "128k" // 128kbps bitrate
      );
    } else {
      ffmpegArgs.push("-an"); // No audio
    }

    // Add faststart for streaming capability
    ffmpegArgs.push("-movflags", "+faststart");

    // Set output file
    ffmpegArgs.push(outputPath);

    // Start the process using the process manager
    return this.processManager.startProcess(processName, ffmpegArgs);
  }

  /**
   * Start recording a webcam
   */
  private async startWebcamRecording(
    item: WebcamRecordingItem,
    outputPath: string
  ): Promise<void> {
    const processName = `webcam-${item.camera.id}`;

    // Get camera device index
    const cameraIndex = await getDeviceIndex("camera");

    if (cameraIndex === null) {
      throw new Error("Could not find camera device");
    }

    const ffmpegArgs: string[] = ["-f", "avfoundation", "-framerate", "30"];

    // Check if microphone is enabled
    if (item.microphone) {
      const microphoneIndex = await getDeviceIndex("microphone");

      if (microphoneIndex !== null) {
        ffmpegArgs.push("-i", `${cameraIndex}:${microphoneIndex}`);
      } else {
        console.warn("Requested microphone not found, recording without audio");
        ffmpegArgs.push("-i", `${cameraIndex}:`);
      }
    } else {
      ffmpegArgs.push("-i", `${cameraIndex}:`);
    }

    // Add video settings for webcam (optimize for lower CPU usage)
    ffmpegArgs.push(
      "-r",
      "30", // 30fps
      "-c:v",
      "libx264", // H.264 codec
      "-preset",
      "ultrafast", // Prioritize speed over quality
      "-crf",
      "28", // Lower quality for better performance
      "-profile:v",
      "baseline", // Baseline profile for better compatibility
      "-pix_fmt",
      "yuv420p" // Standard pixel format
    );

    // Add audio settings if microphone is enabled
    if (item.microphone) {
      ffmpegArgs.push(
        "-c:a",
        "aac", // AAC audio codec
        "-b:a",
        "128k" // 128kbps bitrate
      );
    } else {
      ffmpegArgs.push("-an"); // No audio
    }

    // Add faststart for streaming capability
    ffmpegArgs.push("-movflags", "+faststart");

    // Set output file
    ffmpegArgs.push(outputPath);

    // Start the process using the process manager
    return this.processManager.startProcess(processName, ffmpegArgs);
  }
}
