import fs from 'fs';
import path from 'path';

const CONTENT_DIR = path.join(__dirname, '../content');
// Directory containing the roadmaps
const ROADMAP_CONTENT_DIR = path.join(__dirname, '../content/roadmaps');
const roadmapKey = process.argv[2];

type ControlType = {
  ID: string;
  typeID: string;
  zOrder: string;
  w: string;
  h: string;
  measuredW: string;
  measuredH: string;
  x: string;
  y: string;
  properties: {
    controlName: string;
  };
  children: {
    controls: {
      control: ControlType[];
    };
  };
};

type RoadmapType = {
  mockup: {
    controls: {
      control: ControlType[];
    };
  };
};

const roadmaps: Record<string, RoadmapType> = {
  frontend: require('../public/project/frontend.json'),
  backend: require('../public/project/backend.json'),
  devops: require('../public/project/devops.json'),
  android: require('../public/project/android.json'),
  golang: require('../public/project/golang.json'),
  java: require('../public/project/java.json'),
  python: require('../public/project/python.json'),
  react: require('../public/project/react.json'),
  angular: require('../public/project/angular.json'),
};

if (!roadmapKey || !roadmaps[roadmapKey]) {
  console.error(`Invalid roadmap key ${roadmapKey}`);
  console.error(`Allowed keys are ${Object.keys(roadmaps).join(', ')}`);
  process.exit(1);
}

// Directory holding the roadmap content files
const roadmapDirName = fs
  .readdirSync(ROADMAP_CONTENT_DIR)
  .find((dirName: string) => dirName.replace(/\d+-/, '') === roadmapKey);

if (!roadmapDirName) {
  console.error('Roadmap directory not found');
  process.exit(1);
}

const roadmapDirPath = path.join(ROADMAP_CONTENT_DIR, roadmapDirName);
const roadmapContentDirPath = path.join(
  ROADMAP_CONTENT_DIR,
  roadmapDirName,
  'content'
);

// If roadmap content already exists do not proceed as it would override the files
if (fs.existsSync(roadmapContentDirPath)) {
  console.error(`Roadmap content already exists @ ${roadmapContentDirPath}`);
  process.exit(1);
}

// Type representing the nested dir tree
// {
//    frontend: {
//      internet: {
//        dns-how-does-it-work: {}
//        what-is-domain-name: {}
//      },
//      html: {},
//      css: {
//          making-layouts: {}
//      },
//    }
// }
type DirTreeType = {
  [dirName: string]: DirTreeType;
};

// Hashmap containing the dir key to sort order mapping
// {
//   "frontend": 100,
//   "frontend:internet": 200,
//   "frontend:internet:dns-how-does-it-work": 100
// }
type DirSortOrdersType = Record<string, number>;

function prepareDirTree(
  control: ControlType,
  dirTree: DirTreeType,
  dirSortOrders: DirSortOrdersType
) {
  // Directories are only created for groups
  if (control.typeID !== '__group__') {
    return;
  }

  // e.g. 104-testing-your-apps:other-options
  const controlName = control?.properties?.controlName || '';
  // e.g. 104
  const sortOrder = controlName.match(/^\d+/)?.[0];

  // No directory for a group without control name
  if (!controlName || !sortOrder) {
    return;
  }

  // e.g. testing-your-apps:other-options
  const controlNameWithoutSortOrder = controlName.replace(/^\d+-/, '');
  // e.g. ['testing-your-apps', 'other-options']
  const dirParts = controlNameWithoutSortOrder.split(':');

  // Nest the dir path in the dirTree
  let currDirTree = dirTree;
  dirParts.forEach((dirPart) => {
    currDirTree[dirPart] = currDirTree[dirPart] || {};
    currDirTree = currDirTree[dirPart];
  });

  dirSortOrders[controlNameWithoutSortOrder] = Number(sortOrder);

  const childrenControls = control.children.controls.control;
  // No more children
  if (childrenControls.length) {
    childrenControls.forEach((childControl) => {
      prepareDirTree(childControl, dirTree, dirSortOrders);
    });
  }

  return { dirTree, dirSortOrders };
}

const roadmap = roadmaps[roadmapKey];
const controls = roadmap.mockup.controls.control;

// Prepare the dir tree that we will be creating and also calculate the sort orders
const dirTree: DirTreeType = {};
const dirSortOrders: DirSortOrdersType = {};

controls.forEach((control) => {
  prepareDirTree(control, dirTree, dirSortOrders);
});

/**
 * @param parentDir Parent directory in which directory is to be created
 * @param dirTree Nested dir tree to be created
 * @param sortOrders Mapping from groupName to sort order
 * @param filePaths The mapping from groupName to file path
 */
function createDirTree(
  parentDir: string,
  dirTree: DirTreeType,
  sortOrders: DirSortOrdersType,
  filePaths: Record<string, string> = {}
) {
  const childrenDirNames = Object.keys(dirTree);
  const hasChildren = childrenDirNames.length !== 0;

  // @todo write test for this, yolo for now
  const groupName = parentDir
    .replace(roadmapContentDirPath, '') // Remove base dir path
    .replace(/(^\/)|(\/$)/g, '') // Remove trailing slashes
    .replace(/(^\d+?-)/g, '') // Remove sorting information
    .replaceAll('/', ':') // Replace slashes with `:`
    .replace(/:\d+-/, ':');

  const humanizedGroupName = groupName
    .split(':')
    .pop()
    ?.replaceAll('-', ' ')
    .replace(/^\w/, ($0) => $0.toUpperCase());

  const sortOrder = sortOrders[groupName] || '';

  // Attach sorting information to dirname
  // e.g. /roadmaps/100-frontend/content/internet
  // ———> /roadmaps/100-frontend/content/103-internet
  if (sortOrder) {
    parentDir = parentDir.replace(/(.+?)([^\/]+)?$/, `$1${sortOrder}-$2`);
  }

  // If no children, create a file for this under the parent directory
  if (!hasChildren) {
    let fileName = `${parentDir}.md`;
    fs.writeFileSync(fileName, `# ${humanizedGroupName}`);

    filePaths[groupName || 'home'] = fileName.replace(CONTENT_DIR, '');
    return filePaths;
  }

  // There *are* children, so create the parent as a directory
  // and create `readme.md` as the content file for this
  fs.mkdirSync(parentDir);

  let readmeFilePath = path.join(parentDir, 'readme.md');
  fs.writeFileSync(readmeFilePath, `# ${humanizedGroupName}`);

  filePaths[groupName || 'home'] = readmeFilePath.replace(CONTENT_DIR, '');

  // For each of the directory names, create a
  // directory inside the given directory
  childrenDirNames.forEach((dirName) => {
    createDirTree(
      path.join(parentDir, dirName),
      dirTree[dirName],
      dirSortOrders,
      filePaths
    );
  });

  return filePaths;
}

// Create directories and get back the paths for created directories
const filePaths = createDirTree(roadmapContentDirPath, dirTree, dirSortOrders);
const contentPathsFilePath = path.join(roadmapDirPath, 'content-paths.json');

fs.writeFileSync(contentPathsFilePath, JSON.stringify(filePaths, null, 2));

const roadmapMetaFilePath = path.join(roadmapDirPath, 'meta.json');
const roadmapMeta = require(roadmapMetaFilePath);

// Put the content paths file path in the roadmap meta
roadmapMeta.contentPathsFilePath = contentPathsFilePath.replace(
  roadmapDirPath,
  '.'
);

fs.writeFileSync(roadmapMetaFilePath, JSON.stringify(roadmapMeta, null, 2));
