const fs = require('fs-extra');
const path = require('node:path');

async function pathExists(target) {
  return fs.pathExists(target);
}

async function isDirectoryEmpty(target) {
  const entries = await fs.readdir(target);
  return entries.length === 0;
}

async function ensureDir(target) {
  await fs.ensureDir(target);
}

async function copyDir(source, destination) {
  await fs.copy(source, destination);
}

async function walkFiles(rootDir) {
  const entries = await fs.readdir(rootDir, { withFileTypes: true });
  const files = [];

  for (const entry of entries) {
    const absPath = path.join(rootDir, entry.name);
    if (entry.isDirectory()) {
      files.push(...(await walkFiles(absPath)));
      continue;
    }

    if (entry.isFile()) {
      files.push(absPath);
    }
  }

  return files;
}

const pathNormalize = (p) => String(p || '').replace(/\\/g, '/');

function matchesList(relativePath, list) {
  if (!list || list.length === 0) return false;
  const normalized = pathNormalize(relativePath);
  return list.some((item) => normalized === pathNormalize(item) || normalized.endsWith('/' + pathNormalize(item)));
}

async function mergeDirNoConflicts(source, destination, options = {}) {
  const { exclude = [], overwrite = [] } = options;
  const sourceExists = await pathExists(source);
  if (!sourceExists) {
    return;
  }

  await ensureDir(destination);
  const sourceFiles = await walkFiles(source);

  for (const sourceFile of sourceFiles) {
    const relative = path.relative(source, sourceFile).replace(/\\/g, '/');
    if (
      relative === '.dart_tool' || relative.startsWith('.dart_tool/') ||
      relative === '.flutter-plugins' || relative === '.flutter-plugins-dependencies'
    ) {
      continue;
    }
    if (matchesList(relative, exclude)) {
      continue;
    }

    const targetFile = path.join(destination, relative);
    const targetDir = path.dirname(targetFile);
    await ensureDir(targetDir);

    const targetExists = await pathExists(targetFile);
    if (!targetExists) {
      await fs.copy(sourceFile, targetFile);
      continue;
    }

    if (matchesList(relative, overwrite)) {
      await fs.copy(sourceFile, targetFile);
      continue;
    }

    const [sourceContent, targetContent] = await Promise.all([
      fs.readFile(sourceFile),
      fs.readFile(targetFile)
    ]);

    if (!sourceContent.equals(targetContent)) {
      throw new Error(`Patch conflict on file: ${targetFile}`);
    }
  }
}

async function moveDir(source, destination) {
  await fs.move(source, destination);
}

async function readText(target) {
  return fs.readFile(target, 'utf8');
}

async function writeText(target, contents) {
  await fs.writeFile(target, contents, 'utf8');
}

async function writeJson(target, json) {
  await fs.writeJson(target, json, { spaces: 2 });
}

module.exports = {
  pathExists,
  isDirectoryEmpty,
  ensureDir,
  copyDir,
  mergeDirNoConflicts,
  moveDir,
  readText,
  writeText,
  writeJson
};
