import { z } from "zod";
import path from "path";
import fsPromises, { readFile } from "fs/promises";
import chalk from "chalk";
import { logger } from "../utils";

/**
 * Represents a file operation, including modifications, deletes, and moves.
 */
export const FileOperationSchema = z.object({
  /**
   * Absolute path to the file.
   */
  path: z.string(),

  /**
   * FULL CONTENT of the file after modification. Provides the FULL AND FINAL content of
   * the file after modification WITHOUT OMITTING OR TRUNCATING ANY PART OF THE FILE.
   *
   * Mutually exclusive with 'delete' and 'move_to'.
   */
  updated_full_content: z.string().nullable().optional(),

  /**
   * Set to true if the file is to be deleted.
   *
   * Mutually exclusive with 'updated_full_content' and 'move_to'.
   */
  delete: z.boolean().nullable().optional(),

  /**
   * New path of the file if it is to be moved.
   *
   * Mutually exclusive with 'updated_full_content' and 'delete'.
   */
  move_to: z.string().nullable().optional(),
});

export type FileOperation = z.infer<typeof FileOperationSchema>;

/**
 * Container for one or more FileOperation objects.
 */
export const EditedFilesSchema = z.object({
  /**
   * A list of file operations that are applied in order.
   */
  ops: z.array(FileOperationSchema),
});

export type EditedFiles = z.infer<typeof EditedFilesSchema>;

export async function applyFileOps(ops: Array<FileOperation>) {
  logger.info("[FILE_OPS] appying file operations.")
  for (const op of ops) {
    if (op.delete) {
      logger.info("[FILE_OPS] Deleting file: " + op.path);
      try {
        await fsPromises.unlink(op.path);
      } catch (err) {
        logger.error(chalk.red("[FILE_OPS] Failure in applying fileOp delete: ") + op.path);
      }
    } else if (op.move_to) {
      logger.info("[FILE_OPS] Moving file: " + op.path + " -> " + op.move_to);
      const newContent = op.updated_full_content || "";
      try {
        await fsPromises.mkdir(path.dirname(op.move_to), { recursive: true });
        await fsPromises.writeFile(op.move_to, newContent, "utf-8");
      } catch (err) {
        logger.error(chalk.red("[FILE_OPS] Failure in applying fileOp move_to: "));
      }
      try {
        logger.info("[FILE_OPS] Finishing move by deleting: " + op.path);
        await fsPromises.unlink(op.path);
      } catch (err) {
        logger.error(chalk.red("[FILE_OPS] Failure in applying fileOp delete for move: ") + op.path)
      }
    } else {
      logger.info("[FILE_OPS] Updating file: " + op.path);
      const newContent = op.updated_full_content || "";
      try {
        await fsPromises.mkdir(path.dirname(op.path), { recursive: true });
        await fsPromises.writeFile(op.path, newContent, "utf-8");
      } catch(err) {
        logger.error(chalk.red("[FILE_OPS] Failure in applying fileOp update") + op.path)
      }
    }
  }
}