// ignore_for_file: public_member_api_docs, sort_constructors_first
// Copyright (C) 2023 WYATT GROUP
// Please see the AUTHORS file for details.
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

import 'dart:io';

import 'package:args/args.dart';
import 'package:brick_generator/core/file_system.dart';
import 'package:brick_generator/core/logger.dart';
import 'package:brick_generator/models/brick_config.dart';
import 'package:path/path.dart';

const _configurationFileName = 'brickgen.yaml';
const _masonConfigurationFileName = 'brick.yaml';
const _brickFolderName = '__brick__';
const _hooksFolderName = 'hooks';

const _helpOption = 'help';
const _verboseOption = 'verbose';
const _removeEmptyOption = 'remove-empty';

class Brickgen {
  final String brickPath;
  final String outputPath;
  final bool deleteEmptyFolders;

  late String configPath;
  late String hooksInputPath;

  String? toBrickifyPath;
  String? masonConfigPath;
  String? targetPath;
  String? hooksOutputPath;

  Brickgen({
    required this.brickPath,
    required this.outputPath,
    required this.deleteEmptyFolders,
  }) {
    configPath = join(brickPath, _configurationFileName);
    hooksInputPath = join(brickPath, _hooksFolderName);
  }

  Future<void> run() async {
    // Load config
    final config = BrickConfig.fromFile(configPath);
    Logger.debug('Read config: \n${config.toMason()}');
    Logger.info('Config retrieved.');

    // Define paths
    toBrickifyPath = FileSystem.joinExists(
      brickPath,
      config.brickgenConfig.pathToBrickify,
    );
    Logger.debug('Define `toBrickifyPath`: $toBrickifyPath');

    masonConfigPath = FileSystem.joinTouch(
      outputPath,
      config.name,
      _masonConfigurationFileName,
    );
    Logger.debug('Define `masonConfigPath`: $masonConfigPath');

    hooksOutputPath = FileSystem.joinRecreate(
      outputPath,
      config.name,
      _hooksFolderName,
    );
    Logger.debug('Define `hooksOutputPath`: $hooksOutputPath');

    targetPath = FileSystem.joinRecreate(
      outputPath,
      config.name,
      _brickFolderName,
    );
    Logger.debug('Define `targetPath`: $targetPath');

    // Check paths
    if (toBrickifyPath == null ||
        targetPath == null ||
        masonConfigPath == null ||
        hooksOutputPath == null) {
      throw Exception('An error occures during path definition.');
    }
    Logger.info('Paths defined.');

    // Copy (and exclude from copy) project files
    await FileSystem.copyFolder(
      toBrickifyPath!,
      targetPath!,
      ignoreList: config.brickgenConfig.ignore,
    );
    Logger.info('Project copied.');

    // Take care of boolean mapping and boolean fs
    FileSystem.applyBooleanFileSystem(config, targetPath!);
    Logger.info('Boolean file system applied.');
    // TODO(wyatt): generate hook to handle boolean mapping.

    // Convert compilable values -> variables (in files)
    FileSystem.convertCompilableVariablesInFolder(config, targetPath!);
    Logger.info('Files content converted with variables.');

    // Create `.gitkeep` in empty folders (to keep them in brick generation)
    // Or remove them (depending of --remove option)
    if (deleteEmptyFolders) {
      FileSystem.removeEmptyFolders(targetPath!);
      Logger.info('Empty folders removed.');
    } else {
      FileSystem.createGitKeepInEmptyFolders(targetPath!);
      Logger.info('Empty folders marked as to keep.');
    }

    // Convert compilable values -> variables (files/folder names)
    FileSystem.renameCompilableFilesInFolder(config, targetPath!);
    Logger.info('Files renamed with variables.');
    
    // Clean empty folders created from copy/moves
    FileSystem.removeEmptyFolders(targetPath!);

    // Create Mason config
    FileSystem.writeFile(masonConfigPath!, config.toMason());
    Logger.info('Mason `brick.yaml` generated.');

    // Copy hooks
    await FileSystem.copyHooks(config, hooksInputPath, hooksOutputPath!);

    // Success!
    Logger.success('Brick template available at $targetPath');
  }
}

Future<void> main(List<String> args) async {
  int exitCode = 0;

  final parser = ArgParser()
    ..addFlag(
      _helpOption,
      negatable: false,
      abbr: 'h',
      help: 'Show this help dialog',
    )
    ..addFlag(
      _verboseOption,
      negatable: false,
      abbr: 'v',
      help: 'Show additional diagnostic info',
    )
    ..addFlag(
      _removeEmptyOption,
      negatable: false,
      abbr: 'r',
      help: 'Remove empty folders (if not it creates '
          '.gitkeep in each empty folders)',
    );

  final argResults = parser.parse(args);

  if (argResults[_helpOption] as bool? ?? false) {
    Logger.info('Brickgen\n${parser.usage}');
    exit(0);
  }

  if (argResults[_verboseOption] as bool? ?? false) {
    Logger.setVerbose();
  }

  final paths = argResults.rest;

  if (paths.length < 2) {
    Logger.error('Please provide input and output paths.');
    if (!(argResults[_helpOption] as bool? ?? false)) {
      Logger.info('Brickgen\n${parser.usage}');
    }
    exitCode = 1;
    exit(exitCode);
  }

  final brickPath = paths[0];
  final outputPath = paths[1];

  await Brickgen(
    brickPath: brickPath,
    outputPath: outputPath,
    deleteEmptyFolders: argResults[_removeEmptyOption] as bool? ?? false,
  ).run();

  exit(exitCode);
}
