#!/usr/bin/env node
import fs from 'node:fs/promises';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
import { spawn } from 'node:child_process';
import readline from 'node:readline/promises';

const argv = process.argv.slice(2);

let pm = 'pnpm';
let targetDir;
let shouldInstall;
let shouldInitGit;
let shouldIncludeGis;
const flags = new Set();

for (let i = 0; i < argv.length; i += 1) {
  const arg = argv[i];
  if (arg === '--pm') {
    pm = argv[i + 1] ?? pm;
    i += 1;
    continue;
  }
  if (arg.startsWith('--pm=')) {
    pm = arg.slice('--pm='.length) || pm;
    continue;
  }
  if (arg === '--install') {
    shouldInstall = true;
    continue;
  }
  if (arg === '--no-install') {
    shouldInstall = false;
    continue;
  }
  if (arg === '--git') {
    shouldInitGit = true;
    continue;
  }
  if (arg === '--no-git') {
    shouldInitGit = false;
    continue;
  }
  if (arg === '--gis') {
    shouldIncludeGis = true;
    continue;
  }
  if (arg === '--no-gis') {
    shouldIncludeGis = false;
    continue;
  }
  if (arg.startsWith('-')) {
    flags.add(arg);
    continue;
  }
  if (!targetDir) targetDir = arg;
}

if (flags.has('--help') || flags.has('-h')) {
  console.log(
    `\nUsage: pnpm create swdg [project-name] [--install|--no-install] [--git|--no-git] [--gis|--no-gis] [--pm pnpm|npm|yarn]\n`
  );
  process.exit(0);
}

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const templateDir = path.resolve(__dirname, '../template');
let destDir;

function isValidPackageName(name) {
  if (!name) return false;
  if (name.startsWith('@')) return false;
  if (name.length > 214) return false;
  return /^[a-z0-9][a-z0-9._-]*$/.test(name);
}

async function resolveTargetDir() {
  if (targetDir) {
    if (!isValidPackageName(targetDir)) {
      console.error(
        'Invalid project name. Use lowercase letters, numbers, dots, dashes or underscores.'
      );
      process.exit(1);
    }
    return targetDir;
  }
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  let name = '';
  while (!isValidPackageName(name)) {
    const answer = await rl.question('Project name (my-app): ');
    name = answer.trim() || 'my-app';
    if (!isValidPackageName(name)) {
      console.log(
        'Invalid project name. Use lowercase letters, numbers, dots, dashes or underscores.'
      );
    }
  }
  rl.close();
  return name;
}

async function resolveGisChoice() {
  if (shouldIncludeGis === true) return true;
  if (shouldIncludeGis === false) return false;
  const rl = readline.createInterface({
    input: process.stdin,
    output: process.stdout,
  });
  const answer = await rl.question('Include GIS (Cesium) assets? (Y/n): ');
  rl.close();
  if (!answer.trim()) return true;
  return /^y(es)?$/i.test(answer.trim());
}

async function pathExists(p) {
  try {
    await fs.access(p);
    return true;
  } catch {
    return false;
  }
}

async function isDirEmpty(p) {
  try {
    const entries = await fs.readdir(p);
    return entries.length === 0;
  } catch {
    return true;
  }
}

async function copyTemplate() {
  if (await pathExists(destDir)) {
    const empty = await isDirEmpty(destDir);
    if (!empty) {
      const rl = readline.createInterface({
        input: process.stdin,
        output: process.stdout,
      });
      const answer = await rl.question(
        `Target directory is not empty. Remove and continue? (y/N): `
      );
      rl.close();
      if (!/^y(es)?$/i.test(answer.trim())) {
        console.error('Aborted.');
        process.exit(1);
      }
      await fs.rm(destDir, { recursive: true, force: true });
      await fs.mkdir(destDir, { recursive: true });
    }
  } else {
    await fs.mkdir(destDir, { recursive: true });
  }

  await fs.cp(templateDir, destDir, { recursive: true });
}

async function updatePackageName() {
  const pkgPath = path.join(destDir, 'package.json');
  const raw = await fs.readFile(pkgPath, 'utf8');
  const pkg = JSON.parse(raw);
  pkg.name = path.basename(destDir);
  await fs.writeFile(pkgPath, JSON.stringify(pkg, null, 2) + '\n', 'utf8');
}

async function updateIndexTitle() {
  const indexPath = path.join(destDir, 'index.html');
  try {
    const html = await fs.readFile(indexPath, 'utf8');
    const title = path.basename(destDir);
    const next = html.replace(
      /<title>.*?<\/title>/i,
      `<title>${title}</title>`
    );
    await fs.writeFile(indexPath, next, 'utf8');
  } catch {
    // Ignore if index.html does not exist
  }
}

async function removeGisAssets() {
  const gisDir = path.join(destDir, 'public', 'Cesium1.119');
  await fs.rm(gisDir, { recursive: true, force: true });
}

async function runInstall() {
  const cmd = pm || 'pnpm';
  const child = spawn(cmd, ['install'], {
    cwd: destDir,
    stdio: 'inherit',
  });
  await new Promise((resolve, reject) => {
    child.on('exit', (code) => {
      if (code === 0) resolve();
      else reject(new Error(`${cmd} install failed with code ${code}`));
    });
  });
}

async function initGit() {
  await new Promise((resolve, reject) => {
    const child = spawn('git', ['init'], {
      cwd: destDir,
      stdio: 'inherit',
    });
    child.on('exit', (code) => {
      if (code === 0) resolve();
      else reject(new Error(`git init failed with code ${code}`));
    });
  });
}

try {
  targetDir = await resolveTargetDir();
  destDir = path.resolve(process.cwd(), targetDir);
  await copyTemplate();
  await updatePackageName();
  await updateIndexTitle();

  const includeGis = await resolveGisChoice();
  if (!includeGis) {
    await removeGisAssets();
  }

  if (shouldInitGit === true) {
    await initGit();
  }

  if (
    shouldInstall === true ||
    (shouldInstall !== false && flags.has('--install'))
  ) {
    await runInstall();
  }

  console.log(`\nProject created at ${destDir}`);
  console.log(
    `\nNext steps:\n  cd ${targetDir}\n  ${pm} install\n  ${pm} run dev\n`
  );
} catch (err) {
  console.error(err?.message ?? err);
  process.exit(1);
}
