import Logger from '@/logger';
import chokidar from 'chokidar';
import fs from 'fs';
import _ from 'lodash';
import path from 'path';
import rimraf from 'rimraf';

// objects to store cached data
const cachedTimes: Record<string, number> = {};
const cachedJSON: Record<string, unknown> = {};

// read a file and write it to cache, return the json object
function updateJSONCache(file: string) {
  let body;
  try {
    // check if the file contents exist
    body = fs.readFileSync(file, 'utf8');
    // sometimes json files start with unicode for some reason. who knows!
    if (!body.startsWith('{') && body.length > 4)
      body = body.replace(/^[^{]+/, '');
    if (!body) return cachedJSON[file];

    // parse them as  json
    cachedJSON[file] = JSON.parse(body);
    cachedTimes[file] = Date.now();
  } catch (err) {
    Logger.errorp('Error updating JSON cache for file', file, err);
    if (body) Logger.errorp('File contents:', body);
  }
  return cachedJSON[file];
}

// read cached json
export function readCachedJSON(file: string, expire = 5000) {
  const now = Date.now();
  if (cachedTimes[file] && cachedTimes[file] + expire > now)
    return cachedJSON[file];

  // update the cache with data
  return updateJSONCache(file);
}

// object state to store watched json data
const watchers: Record<string, chokidar.FSWatcher> = {};

export function readWatchedJSON(file: string) {
  // if the file is already being watched, return the watched json
  if (watchers[file]) return cachedJSON[file];

  // check if the file exists
  if (!fs.existsSync(file)) return undefined;

  // create a watcher (no persistence means the process dies even if there's still a watcher)
  const watcher = chokidar.watch(file, { persistent: false });
  watchers[file] = watcher;

  const read = _.debounce(() => updateJSONCache(file), 500);

  // add listeners to the watcher
  watcher
    .on('add', () => read()) // on add, update the cache
    .on('change', () => read()) // on change, update the cache
    .on('unlink', () => {
      // on unlink (delete), destroy value in cache
      cachedJSON[file] = undefined;
      cachedTimes[file] = Date.now();
    });

  return updateJSONCache(file);
}

// recursively mkdir (mkdir -p )
export function mkdir(path: string) {
  try {
    fs.mkdirSync(path, { recursive: true });
  } catch (e) {
    /* */
  }
}

// rm -rf a path
export function rmdir(dir: string) {
  if (!fs.existsSync(dir)) return false;
  return new Promise((resolve, reject) => {
    rimraf(dir, error => {
      if (error) reject(error);
      else resolve(true);
    });
  });
}

// copy files from one dir to another, creating the directories in the process
export function copyFiles(srcDir: string, dstDir: string, files: string[]) {
  // create the directories if they don't already exist
  mkdir(srcDir);
  mkdir(dstDir);

  // copy the brickadia auth files
  for (const f of files) {
    // source is the omegga config path
    const src = path.join(srcDir, f);

    // destination file is in the auth path
    const dst = path.join(dstDir, f);

    // if it exists in the auth path and not in the current folder
    if (!fs.existsSync(dst) && fs.existsSync(src)) {
      fs.copyFileSync(src, dst);
    }
  }
}