use dotenv::dotenv;
use inquire::{Confirm, Select, Text};
use std::{
    env::{args, var},
    fs::{create_dir_all, remove_dir_all, File},
    io::Write,
    os::unix::fs::symlink,
    path::{absolute, Path},
    process::exit,
};

// Constants
const VERSION: &str = "0.3.1";
const ARGS: [&str; 4] = ["help", "quick", "simple", "full"];
const SIGNATURE: &str = "Generated by psp (https://github.com/MatteoGuadrini/psp)";

#[cfg(any(target_os = "linux", target_os = "macos"))]
const PYTHON_BIN: &str = "python3";
#[cfg(any(target_os = "linux", target_os = "macos"))]
const PIP_BIN: &str = "pip3";
#[cfg(any(target_os = "linux", target_os = "macos"))]
const TOOLS: [&str; 4] = ["python3", "git", "pip3", "curl"];
#[cfg(target_os = "windows")]
const TOOLS: [&str; 4] = ["python.exe", "git.exe", "pip.exe", "powershell.exe"];
#[cfg(target_family = "windows")]
const PYTHON_BIN: &str = "python.exe";
#[cfg(target_family = "windows")]
const PIP_BIN: &str = "pip.exe";

// Utility functions

// Function to split PATH environment variable
fn split_env_path() -> Vec<String> {
    let env_paths = var("PATH").ok().unwrap();
    #[cfg(any(target_os = "linux", target_os = "macos"))]
    let paths: Vec<String> = env_paths.split(':').map(String::from).collect();
    #[cfg(target_os = "windows")]
    let paths: Vec<String> = env_paths.split(';').map(String::from).collect();
    paths
}

fn join_env_path(path: String) -> String {
    let mut paths = split_env_path();
    paths.insert(0, path);
    #[cfg(any(target_os = "linux", target_os = "macos"))]
    let separator: String = String::from(":");
    #[cfg(target_os = "windows")]
    let separator: String = String::from(";");
    paths.join(&separator)
}

// Function for check if tool is installed
fn check_tool(tool: &str) {
    let paths: Vec<String> = split_env_path();
    for path in paths {
        let tool_path = format!("{path}/{tool}");
        if Path::new(&tool_path).exists() {
            return;
        }
    }
    eprintln!("error: `{}` is not installed", tool);
    exit(1);
}

// Function for creating folders and parents
fn make_dirs(dir: &str) -> std::io::Result<()> {
    let result = create_dir_all(dir);
    if let Err(e) = result {
        eprintln!("error: {}", e);
        exit(2);
    } else {
        Ok(())
    }
}

// Function for creating a file with contents
fn make_file(file: &str, content: String) -> std::io::Result<()> {
    let mut file = File::create(file)?;
    let result = file.write_all(&content.as_bytes());
    if let Err(e) = result {
        eprintln!("error: {}", e);
        exit(3);
    } else {
        Ok(())
    }
}

fn get_python_version() -> String {
    let mut python = make_command(PYTHON_BIN, ".", ".", vec!["--version".to_string()], false);
    let output = python
        .output()
        .expect(format!("{PYTHON_BIN} should be installed").as_str());
    let version = std::str::from_utf8(&output.stdout)
        .unwrap()
        .replace("Python", "")
        .replace(" ", "")
        .replace("\n", "");
    format!("{}", &version[..4])
}

// Function for prompt text
fn prompt_text(question: &str, default: &str, help: &str) -> String {
    let answer = if help != "None" && default != "None" {
        Text::new(question)
            .with_help_message(help)
            .with_default(default)
            .prompt()
    } else if help != "None" {
        Text::new(question).with_help_message(help).prompt()
    } else if default != "None" {
        Text::new(question).with_default(default).prompt()
    } else {
        Text::new(question).prompt()
    };
    answer.unwrap().to_string()
}

// Function for prompt confirmation (yes/no)
fn prompt_confirm(question: &str, default: bool, help: &str) -> bool {
    let answer = if help != "None" {
        Confirm::new(question)
            .with_default(default)
            .with_help_message(help)
            .prompt()
    } else {
        Confirm::new(question).with_default(default).prompt()
    };
    answer.unwrap()
}

// Function for prompt selections
fn prompt_select(question: &str, options: Vec<&str>, help: &str) -> String {
    let answer = if help != "None" {
        Select::new(question, options)
            .with_help_message(help)
            .prompt()
    } else {
        Select::new(question, options).prompt()
    };
    answer.unwrap().to_string()
}

// Function to print help
fn print_help(exit_code: i32) {
    // Collect PSP_* active variables
    let mut psp_vars: Vec<String> = vec![];
    for var in dotenv::vars() {
        if var.0.contains("PSP_") {
            psp_vars.push(var.0);
        }
    }
    println!("psp (Python Scaffolding Projects), version {VERSION}");
    println!("usage: psp [shortcut]");
    println!("ie: psp [help|quick|simple|full]");
    // Print complete help
    if exit_code == 0 {
        println!(
            "
shortcut:
    help:   print this help message
    quick:  enables a rapid setup (few options included)
    simple: enables a basic setup (only Python package)
    full:   enables a full setup (all options included)

links:
    repository:     https://github.com/MatteoGuadrini/psp
    documentation:  https://psp.readthedocs.io/

variables:
    {psp_vars:?}

environments:
    Python version: {}",
            get_python_version()
        );
    }
    exit(exit_code)
}

// Function that captures keyword argument
fn get_shortcut() -> String {
    let args: Vec<String> = args().collect();
    if args.len() > 1 {
        let shortcut = &args[1];
        if !ARGS.contains(&shortcut.as_str()) {
            eprintln!("error: unknown shortcut command `{}`", shortcut);
            print_help(1);
        }
        shortcut.clone()
    } else {
        "None".to_string()
    }
}

// Function that load env files
fn load_env() {
    // Load first, .env file from the current working directory
    dotenv().ok();
    // Load second, .psp.env file from home
    #[cfg(any(target_os = "linux", target_os = "macos"))]
    let home_var_name = "HOME";
    #[cfg(target_os = "windows")]
    let home_var_name = "USERPROFILE";
    let home_var = var(home_var_name).unwrap();
    let home_env = Path::new(home_var.as_str()).join(".psp.env");
    dotenv::from_filename(home_env).ok();
}

// Function that make a command
fn make_command(
    bin: &str,
    root: &str,
    start_path: &str,
    args: Vec<String>,
    venv: bool,
) -> std::process::Command {
    let mut command = std::process::Command::new(bin);
    // Activate venv
    #[cfg(any(target_os = "linux", target_os = "macos"))]
    let bin_folder = "bin";
    #[cfg(target_os = "windows")]
    let bin_folder = "Scripts";
    if venv {
        command.env(
            "PATH",
            join_env_path(
                Path::new(absolute(root).unwrap().display().to_string().as_str())
                    .join("venv")
                    .join(bin_folder)
                    .display()
                    .to_string(),
            ),
        );
    }
    command.args(&args).current_dir(start_path);
    command
}

// Core functions

// Project name
fn prj_name() -> (String, String) {
    // Check environment variable
    let env_name = var("PSP_NAME").ok();
    let name = if let Some(env_name) = env_name {
        env_name
    } else {
        #[cfg(any(target_os = "linux", target_os = "macos"))]
        let folder_separator = "/";
        #[cfg(target_os = "windows")]
        let folder_separator = "\\";
        prompt_text("Name of Python project:", "None", "Type name or path")
            .trim()
            .trim_end_matches(folder_separator)
            .to_string()
    };
    // Check is path is empty
    if name.is_empty() {
        eprintln!("error: empty path is not allowed");
        exit(1)
    }
    // Make package path parts
    let project_name = name.replace(" ", "_");
    let project = Path::new(&project_name);
    let package = project.join(
        project
            .file_name()
            .unwrap()
            .to_string_lossy()
            .to_string()
            .to_lowercase(),
    );
    let root = Path::new(package.parent().unwrap());
    // Check if a project path already exists
    if package.exists() {
        let project_exists = prompt_confirm(
            format!("Path `{}` exists. Do you want continue?", root.display()).as_str(),
            false,
            "Some files will be overwritten",
        );
        if !project_exists {
            println!("info: the path `{}` unchanged", root.display());
            exit(0)
        }
    }
    let dir_ret = make_dirs(format!("{}", package.display()).as_str());
    if let Err(e) = dir_ret {
        eprintln!("error: {}", e);
    }
    // Check version of Python project
    let env_pyver = var("PSP_PYVER").ok();
    let pyver = if let Some(ver) = env_pyver {
        ver
    } else {
        "0.0.1".to_string()
    };
    // Make file structures
    let init_file = package.join("__init__.py");
    let file_ret = make_file(
        format!("{}", init_file.display()).as_str(),
        format!(
            "#! /usr/bin/env python3
# -*- encoding: utf-8 -*-
# vim: se ts=4 et syn=python:
# {SIGNATURE}, version {VERSION}

__version__ = '{pyver}'
"
        ),
    );
    if let Err(e) = file_ret {
        eprintln!("error: {}", e);
        exit(4);
    }
    let main_file = package.join("__main__.py");
    let main_file_ret = make_file(
        format!("{}", main_file.display()).as_str(),
        format!(
            "#! /usr/bin/env python3
# -*- encoding: utf-8 -*-
# vim: se ts=4 et syn=python:
# {SIGNATURE}, version {VERSION}

from .__init__ import __version__

print('name: {}')
print(f'version: {{__version__}}')
",
            package
                .file_name()
                .unwrap()
                .to_string_lossy()
                .to_string()
                .to_lowercase()
        ),
    );
    if let Err(e) = main_file_ret {
        eprintln!("error: {}", e);
        exit(4);
    }
    (
        root.to_string_lossy().to_string(),
        package.file_name().unwrap().to_string_lossy().to_string(),
    )
}

// Project git
fn prj_git(name: &str, shortcut: &String) -> bool {
    // Check environment variable
    let env_git = var("PSP_GIT").unwrap_or("false".to_string()).parse().ok();
    let confirm = if let Some(true) = env_git {
        true
    } else if shortcut == "quick" || shortcut == "full" {
        true
    } else if shortcut == "simple" {
        false
    } else {
        prompt_confirm("Do you want to start git repository?", true, "None")
    };
    if confirm {
        let mut git = make_command("git", name, name, vec!["init".to_string()], false);
        let output = git.output().expect("git should be installed");
        // Check if command exit successfully
        if !output.status.success() {
            eprintln!("error: something wrong with `git init`");
            return false;
        }
        // Create .gitignore a file
        let file_ret = make_file(
            format!("{name}/.gitignore").as_str(),
            format!(
                "# {SIGNATURE}, version {VERSION}
### Python ###
# JetBrains IDEs
.idea/

# CMake
cmake-build-*/

# File-based project format
*.iws

# mpeltonen/sbt-idea plugin
.idea_modules/

# JIRA plugin
atlassian-ide-plugin.xml

# Crashlytics plugin (for Android Studio and IntelliJ)
com_crashlytics_export_strings.xml
crashlytics.properties
crashlytics-build.properties
fabric.properties

# Byte-compiled / optimized / DLL files
__pycache__/
*.py[cod]
*$py.class

# C extensions
*.so

# Distribution / packaging
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
share/python-wheels/
*.egg-info/
.installed.cfg
*.egg
MANIFEST

# PyInstaller
*.manifest
*.spec

# Installer logs
pip-log.txt
pip-delete-this-directory.txt

# Unit test / coverage reports
htmlcov/
.tox/
.nox/
.coverage
.coverage.*
.cache
nosetests.xml
coverage.xml
*.cover
*.py,cover
.hypothesis/
.pytest_cache/
cover/

# Translations
*.mo
*.pot

# Django stuff:
*.log
local_settings.py
db.sqlite3
db.sqlite3-journal

# Flask stuff:
instance/
.webassets-cache

# Scrapy stuff:
.scrapy

# Sphinx documentation
docs/_build/

# PyBuilder
.pybuilder/
target/

# Jupyter Notebook
.ipynb_checkpoints

# IPython
profile_default/
ipython_config.py

# pdm
.pdm.toml

# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
__pypackages__/

# Celery stuff
celerybeat-schedule
celerybeat.pid

# SageMath parsed files
*.sage.py

# Environments
.env
.venv
env/
venv/
ENV/
env.bak/
venv.bak/

# Spyder project settings
.spyderproject
.spyproject

# Rope project settings
.ropeproject

# mkdocs documentation
/site

# mypy
.mypy_cache/
.dmypy.json
dmypy.json

# Pyre type checker
.pyre/

# pytype static type analyzer
.pytype/

# Cython debug symbols
cython_debug/

# Poetry local configuration file - https://python-poetry.org/docs/configuration/#local-configuration
poetry.toml

# ruff
.ruff_cache/

# LSP config files
pyrightconfig.json

# Vim Swap
[._]*.s[a-v][a-z]
!*.svg  # comment out if you don't need vector files
[._]*.sw[a-p]
[._]s[a-rt-v][a-z]
[._]ss[a-gi-z]
[._]sw[a-p]
Session.vim
Sessionx.vim
.netrwhist
*~
tags
[._]*.un~

# VisualStudioCode
.vscode/

# Local History for Visual Studio Code
.history/

# Built Visual Studio Code Extensions
*.vsix

# Ignore all local history of files
.history
.ionide"
            ),
        );
        let ret = if let Err(e) = file_ret {
            eprintln!("error: {}", e);
            false
        } else {
            true
        };
        return ret;
    }
    false
}

// Project unit tests
fn prj_test(root: &str, name: &str, shortcut: &String) -> bool {
    // Check environment variable
    let env_test = var("PSP_TEST").unwrap_or("false".to_string()).parse().ok();
    let confirm = if let Some(true) = env_test {
        true
    } else if shortcut != "None" {
        true
    } else {
        prompt_confirm("Do you want unit test files?", true, "None")
    };
    if confirm {
        // Check version of Python project
        let env_pyver = var("PSP_PYVER").ok();
        let pyver = if let Some(ver) = env_pyver {
            ver
        } else {
            "0.0.1".to_string()
        };
        let project_name = name.to_lowercase();
        // Make directories structure
        let tests_dir = Path::new(root).join("tests");
        let dir_ret = make_dirs(tests_dir.display().to_string().as_str());
        if let Err(e) = dir_ret {
            eprintln!("error: {}", e);
            return false;
        }
        // Make file structures
        let init_file = tests_dir.join("__init__.py");
        let init_file_ret = make_file(
            init_file.display().to_string().as_str(),
            format!(
                "#! /usr/bin/env python3
# -*- encoding: utf-8 -*-
# vim: se ts=4 et syn=python:
# {SIGNATURE}, version {VERSION}


"
            ),
        );
        if let Err(e) = init_file_ret {
            eprintln!("error: {}", e);
            return false;
        }
        let all_module = tests_dir.join(format!("test_{project_name}.py").as_str());
        let all_module_ret = make_file(
            all_module.display().to_string().as_str(),
            format!(
                "#! /usr/bin/env python3
# -*- encoding: utf-8 -*-
# vim: se ts=4 et syn=python:
# {SIGNATURE}, version {VERSION}


import unittest
from {project_name} import __version__


class TestAll(unittest.TestCase):

    def test_all(self):
        self.assertEqual(__version__, '{pyver}')
        print('Test all {project_name} successfully!')


# Test functions for pytest
def test_all():
    assert __version__ == '{pyver}'


if __name__ == '__main__':
    unittest.main()"
            )
            .to_string(),
        );
        if let Err(e) = all_module_ret {
            eprintln!("error: {}", e);
            return false;
        }
        true
    } else {
        false
    }
}

// Project venv
fn prj_venv(name: &str, shortcut: &String) -> bool {
    // Check environment variable
    let env_venv = var("PSP_VENV").unwrap_or("false".to_string()).parse().ok();
    let confirm = if let Some(true) = env_venv {
        true
    } else if shortcut == "quick" || shortcut == "full" {
        true
    } else if shortcut == "simple" {
        false
    } else {
        prompt_confirm("Do you want to create a virtual environment?", true, "None")
    };
    if confirm {
        let mut python = make_command(
            PYTHON_BIN,
            name,
            name,
            vec!["-m".to_string(), "venv".to_string(), "venv".to_string()],
            false,
        );
        let output = python
            .output()
            .expect(format!("{PYTHON_BIN} should be installed").as_str());
        // Check if command exit successfully
        if !output.status.success() {
            eprintln!("error: `venv` creation failed");
        } else {
            return true;
        }
    }
    false
}

// Project dependencies
fn prj_deps(name: &str, venv: bool, shortcut: &String) -> Vec<String> {
    // Check environment variable
    let env_common_deps = var("PSP_COMMON_DEPS").ok();
    let env_deps = var("PSP_DEPS").ok();
    let deps = if let Some(env_deps) = env_deps {
        env_deps
    } else if shortcut == "simple" || shortcut == "quick" {
        "No".to_string()
    } else {
        prompt_text(
            "Install dependencies:",
            "No",
            "Write package(s) separates with spaces or empty",
        )
    };
    // Check if there are common dependencies
    let common_dependencies = if let Some(env_common_deps) = env_common_deps {
        env_common_deps
            .as_str()
            .split_whitespace()
            .map(|s| s.to_string())
            .collect::<Vec<String>>()
    } else {
        vec![]
    };
    // Split String into Vector
    let mut dependencies: Vec<String> = if deps != "No" {
        deps.as_str()
            .split_whitespace()
            .map(|s| s.to_string())
            .collect()
    } else {
        vec![]
    };
    // Extend dependencies
    if !common_dependencies.is_empty() {
        dependencies.extend(common_dependencies);
    }
    if !dependencies.is_empty() {
        let mut args = vec![
            "install".to_string(),
            "--timeout=10".to_string(),
            "--retries=1".to_string(),
        ];
        args.extend(dependencies.clone());
        let mut pip = make_command(PIP_BIN, name, name, args, venv);
        let output = pip
            .output()
            .expect(format!("{PIP_BIN} should be installed").as_str());
        // Check if command exit successfully
        if !output.status.success() {
            eprintln!("error: dependencies ({deps}) installation failed");
        }
        // Build a requirements.txt file
        let content = format!(
            "# {SIGNATURE}, version {VERSION}\n\n{}",
            dependencies.join("\n")
        );
        let requirements_file = Path::new(name);
        let requirements = make_file(
            requirements_file
                .join("requirements.txt")
                .display()
                .to_string()
                .as_str(),
            content,
        );
        if let Err(e) = requirements {
            eprintln!("error: {}", e);
        }
    }
    dependencies
}

// Project pyproject.toml
fn prj_toml(
    root: &str,
    name: &str,
    deps: &Vec<String>,
    git_info: (String, String),
    mut license: String,
) {
    // Check git information
    let mut documentation = "https://docs.python.org/3/".to_string();
    let mut repository = "https://github.com/python".to_string();
    let mut changelog = "https://docs.python.org/3/whatsnew/changelog.html".to_string();
    // Check author of Python project
    let env_pyauthor = var("PSP_PYAUTHOR").ok();
    let pyauthor = if let Some(author) = env_pyauthor {
        author
    } else {
        "psp@python.com".to_string()
    };
    // Check homepage of Python project
    let env_pyhomepage = var("PSP_PYHOMEPAGE").ok();
    let pyhomepage = if let Some(homepage) = env_pyhomepage {
        homepage
    } else {
        "https://python.org/".to_string()
    };
    // Check description of Python project
    let env_pydescription = var("PSP_PYDESC").ok();
    let pydescription = if let Some(description) = env_pydescription {
        description
    } else {
        format!("{} Python package", name.to_lowercase())
    };
    let description = pydescription;
    let mut homepage = pyhomepage;
    let mut email = pyauthor;
    let mut username = email.split("@").collect::<Vec<&str>>()[0].to_string();
    if git_info.0 != "None" && git_info.1 != "None" {
        let git_repo = &git_info.0.to_lowercase();
        let git_user = &git_info.1.to_lowercase();
        repository = format!(
            "https://{}.com/{}/{}",
            git_repo,
            git_user,
            name.to_lowercase()
        );
        changelog = format!("{}/blob/main/CHANGES.md", &repository);
        if email == "psp@python.com" {
            username = format!("{}", git_user);
            email = format!("{}@{}.com", git_user, git_repo);
        }
        if homepage == "https://python.org/" {
            homepage = format!("https://{}.org/", name.to_lowercase());
        }
        documentation = format!("{}/docs", repository);
    }
    let mut classifiers = vec!["Programming Language :: Python :: 3"];
    // Check dependencies
    let requirements = if deps.contains(&"No".to_string()) {
        "[]".to_string()
    } else {
        format!("{deps:?}")
    };
    // Check license
    if license == "None" {
        license = String::new();
    } else if license == "MIT" {
        license = "MIT License".to_string();
        classifiers.push("License :: OSI Approved :: MIT License")
    } else if license == "Apache" {
        license = "Apache Software License".to_string();
        classifiers.push("License :: OSI Approved :: Apache Software License")
    } else if license == "Creative Commons" {
        license = "Common Public License".to_string();
        classifiers.push("License :: OSI Approved :: Common Public License")
    } else if license == "Mozilla" {
        license = "Mozilla Public License 2.0 (MPL 2.0)".to_string();
        classifiers.push("License :: OSI Approved :: Mozilla Public License 2.0 (MPL 2.0)")
    } else if license == "Gnu Public License" {
        license = "GNU General Public License v3 (GPLv3)".to_string();
        classifiers.push("License :: OSI Approved :: GNU General Public License v3 (GPLv3)")
    }
    // Check version of Python project
    let env_pyver = var("PSP_PYVER").ok();
    let pyver = if let Some(ver) = env_pyver {
        ver
    } else {
        "0.0.1".to_string()
    };
    let content = format!(
        "# {SIGNATURE}, version {VERSION}

[build-system]
requires = ['setuptools', 'wheel']
build-backend = 'setuptools.build_meta'

[project]
name = '{}'
version = '{pyver}'
readme = 'README.md'
license = {{text = '{}'}}
authors = [{{name = '{username}', email = '{email}'}}]
maintainers = [{{name = '{username}', email = '{email}'}}]
description = '{description}'
requires-python = '>={}'
classifiers = {:?}
dependencies = {}

[project.urls]
homepage = '{homepage}'
documentation = '{documentation}'
repository = '{repository}'
changelog = '{changelog}'
",
        name.to_lowercase(),
        license,
        get_python_version(),
        classifiers,
        requirements
    );
    // Write pyproject.toml
    let pyproject = make_file(format!("{root}/pyproject.toml").as_str(), content);
    if let Err(e) = pyproject {
        eprintln!("error: {}", e);
        return;
    }
}

// Project CI
fn prj_ci(name: &str, deps: &Vec<String>, shortcut: &String) {
    let options = vec!["None", "TravisCI", "CircleCI"];
    let env_ci = var("PSP_CI").ok();
    let ci = if let Some(env_ci) = env_ci {
        env_ci
    } else if shortcut == "simple" || shortcut == "quick" {
        "None".to_string()
    } else {
        prompt_select("Select remote CI provider:", options, "None")
    };
    let requirements = if deps.contains(&"No".to_string()) {
        "".to_string()
    } else {
        deps.join(" ")
    };
    // Travis or CircleCI
    if ci.as_str().to_lowercase() == "travisci" {
        let travis_file = Path::new(name).join(".travis.yml");
        let travis = make_file(
            travis_file.display().to_string().as_str(),
            format!(
                "# {SIGNATURE}, version {VERSION}

language: python
cache: pip
python:
  - 3.12
  - 3.13
  - 3.14
before_install:
  - sudo apt-get update
  - sudo apt-get install python3-pip
  - sudo apt-get install python3-pytest
install:
  - pip install {requirements} pipenv
  - pipenv install --dev
script:
  - python -m unittest discover tests
  - pytest tests"
            ),
        );
        if let Err(e) = travis {
            eprintln!("error: {}", e);
        }
    } else if ci.as_str().to_lowercase() == "circleci" {
        let circleci_dir = Path::new(name).join(".circleci");
        let dir_ret = make_dirs(circleci_dir.display().to_string().as_str());
        if let Err(e) = dir_ret {
            eprintln!("error: {}", e);
        }
        let circleci_file = Path::new(circleci_dir.as_path()).join("config.yml");
        let circle = make_file(
            circleci_file.display().to_string().as_str(),
            format!(
                "# {SIGNATURE}, version {VERSION}

version: 2.1
jobs:
  build-and-test:
    docker:
      - image: circleci/python
    steps:
      - checkout
      - run:
          name: Install pytest
          command: pip install pytest
      - run:
          name: Install dependencies
          command: pip install {requirements}
      - run:
          name: Install package
          command: pip install .
      - run:
          name: Run tests
          command: python -m pytest tests
  workflows:
    main:
      jobs:
        - build-and-test
"
            ),
        );
        if let Err(e) = circle {
            eprintln!("error: {}", e);
        }
    } else if ci.as_str().to_lowercase() != "none" {
        println!("warning: `{}` is not recognized as remote CI", ci)
    }
}

// Project Gitlab/GitHub
fn prj_remote(root: &str, name: &str, shortcut: &String) -> (String, String) {
    let mut git_user = "None".to_string();
    let mut git_remote = "None".to_string();
    let options = vec!["None", "Gitlab", "Github"];
    // Check environment variable
    let env_remote = var("PSP_GIT_REMOTE").ok();
    let remote = if let Some(env_remote) = env_remote {
        env_remote
    } else if shortcut == "quick" {
        "None".to_string()
    } else {
        prompt_select("Select git remote provider:", options, "None")
    };
    if remote.as_str().to_lowercase() != "none" {
        // Username of remote git service
        // Check environment variable
        let env_git_user = var("PSP_GIT_USER").ok();
        let mut username = if let Some(env_git_user) = env_git_user {
            env_git_user
        } else {
            prompt_text(
                format!("Username of {remote}:").as_str(),
                "None",
                "The username must not be empty",
            )
        };
        while username.is_empty() {
            username = prompt_text(
                format!("Username of {remote}:").as_str(),
                "None",
                "The username must not be empty",
            );
        }
        git_remote = remote.to_owned();
        git_user = username.to_owned();
        // Add a git remote path
        let remote_path = format!(
            "git@{}.com:{}/{}.git",
            remote.to_lowercase(),
            username,
            name.to_lowercase()
        );
        // Test if remote has already been set
        let origin = std::process::Command::new("git")
            .args(["remote", "-v"])
            .current_dir(root)
            .output()
            .expect("git should be installed");
        let git_verb;
        if origin.stdout.len() > 0 {
            git_verb = "set-url"
        } else {
            git_verb = "add"
        }
        // Set origin remote repository
        let output = std::process::Command::new("git")
            .args(["remote", git_verb, "origin", &remote_path])
            .current_dir(root)
            .output()
            .expect("git should be installed");
        // Check if command exit successfully
        if !output.status.success() {
            eprintln!(
                "error: username of remote repository `{}` setting failed",
                remote.to_lowercase()
            );
        }
        // Make remote files and folders
        // Gitlab
        if remote.as_str().to_lowercase() == "gitlab" {
            let issue_folder = format!("{}/.{}/issue_templates", root, remote.to_lowercase());
            let merge_folder = format!(
                "{}/.{}/merge_request_templates",
                root,
                remote.to_lowercase()
            );
            let dir_ret = make_dirs(issue_folder.as_str());
            if let Err(e) = dir_ret {
                eprintln!("error: {}", e);
            }
            let dir_ret = make_dirs(merge_folder.as_str());
            if let Err(e) = dir_ret {
                eprintln!("error: {}", e);
            }
            let feature_content = format!(
                "<!-- {SIGNATURE}, version {VERSION} -->

## Description

Description of the proposal

## Proposed names of the new function, class or variables of {} package

* function or class name
* possible argument(s)

Additional context

/label ~CR",
                name.to_lowercase()
            );
            let feature_issue = make_file(
                format!("{issue_folder}/feature.md").as_str(),
                feature_content,
            );
            if let Err(e) = feature_issue {
                eprintln!("error: {}", e);
            }
            let bug_content = format!(
                "<!-- {SIGNATURE}, version {VERSION} -->

## Description of problem

Provide a concise description of the bug

## Steps to Reproduce

Lines of code

## Expected Behaviour

Description of what is expected

## Your Environment

* {} version used:
* Operating System and version:

Additional context

/label ~Bug",
                name.to_lowercase()
            );
            let bug_issue = make_file(format!("{issue_folder}/bug.md").as_str(), bug_content);
            if let Err(e) = bug_issue {
                eprintln!("error: {}", e);
            }
            let merge_content = format!(
                "<!-- {SIGNATURE}, version {VERSION} -->

## What does this MR do and why?

%{{first_multiline_commit}}

## MR acceptance checklist of {}

**Please evaluate this MR against the [MR acceptance checklist](https://docs.gitlab.com/ee/development/code_review.html#acceptance-checklist).**
It helps you analyze changes to reduce risks in quality, performance, reliability, security, and maintainability.

## How to set up and validate locally

Numbered steps to set up and validate the change are strongly suggested.

/assign me",
                name.to_lowercase()
            );
            let merge_issue = make_file(format!("{merge_folder}/merge.md").as_str(), merge_content);
            if let Err(e) = merge_issue {
                eprintln!("error: {}", e);
            }
        // Github
        } else if remote.as_str().to_lowercase() == "github" {
            let issue_folder = format!("{}/.{}/ISSUE_TEMPLATE", root, remote.to_lowercase());
            let merge_folder = format!("{}/.{}/PULL_REQUEST_TEMPLATE", root, remote.to_lowercase());
            let dir_ret = make_dirs(issue_folder.as_str());
            if let Err(e) = dir_ret {
                eprintln!("error: {}", e);
            }
            let dir_ret = make_dirs(merge_folder.as_str());
            if let Err(e) = dir_ret {
                eprintln!("error: {}", e);
            }
            let config_content = "blank_issues_enabled: false\n".to_string();
            let config_file = make_file(
                format!("{issue_folder}/config.yml").as_str(),
                config_content,
            );
            if let Err(e) = config_file {
                eprintln!("error: {}", e);
            }
            let feature_content = format!(
                "# {SIGNATURE}, version {VERSION}

name: Feature Request
description: File a feature request.
title: '[Feature]: '
labels: ['enhancement']
assignees:
  - {}
body:
  - type: markdown
    attributes:
      value: '## Feature Request for {}!'
  - type: textarea
    attributes:
      label: Description
      description: A concise description of what you're experiencing.
      placeholder: Description of the proposal
    validations:
      required: true
  - type: textarea
    attributes:
      label: New proposed
      description: Proposed names of the new function, class or variables.
      placeholder: |
        * function or class name
        * possible argument(s)
    validations:
      required: true
  - type: textarea
    attributes:
      label: Additional context
      description: Other considerations
    validations:
      required: false
",
                username,
                name.to_lowercase()
            );
            let feature_issue = make_file(
                format!("{issue_folder}/feature.yml").as_str(),
                feature_content,
            );
            if let Err(e) = feature_issue {
                eprintln!("error: {}", e);
            }
            let bug_content = format!(
                "# {SIGNATURE}, version {VERSION}

name: Bug Report
description: File a bug report.
title: '[Bug]: '
labels: ['bug']
assignees:
  - {}
body:
  - type: markdown
    attributes:
      value: '## Bug Report for {}!'
  - type: textarea
    attributes:
      label: Description of problem
      description: Provide a concise description of the bug.
      placeholder: Describe here the problem
    validations:
      required: true
  - type: textarea
    attributes:
      label: Steps to Reproduce
      description: Lines of code.
      placeholder: Paste here backtrace or lines of code
    validations:
      required: false
  - type: textarea
    attributes:
      label: Expected Behaviour
      description: Description of what is expected.
    validations:
      required: true
  - type: textarea
    attributes:
      label: Your Environment
      description: Description of your environment.
      placeholder: |
        * {} version used:
        * Operating System and version:
    validations:
      required: false
  - type: textarea
    attributes:
      label: Additional context
      description: Other considerations
    validations:
      required: false
",
                username,
                name.to_lowercase(),
                name.to_lowercase()
            );
            let bug_issue = make_file(format!("{issue_folder}/bug.yml").as_str(), bug_content);
            if let Err(e) = bug_issue {
                eprintln!("error: {}", e);
            }
            let merge_content = format!(
                "<!-- {SIGNATURE}, version {VERSION} -->

# Title of Pull Request

---
name: Tracking issue
about: Use this template for tracking new features.
title: '[DATE]: [FEATURE NAME]'
labels: enhancement
assignees: {}
---

## Describe your changes

## Issue ticket number and link

## Checklist before requesting a review
- [ ] I have performed a self-review of my code
- [ ] If it is a core feature, I have added thorough tests.
- [ ] Do we need to implement analytics?
- [ ] Will this be part of a product update? If yes, please write one phrase about this update.
",
                username
            );
            let merge_issue = make_file(
                format!("{merge_folder}/pull_request_template.md").as_str(),
                merge_content,
            );
            if let Err(e) = merge_issue {
                eprintln!("error: {}", e);
            }
        } else {
            println!(
                "warning: `{}` is not recognized as remote git provider",
                remote
            )
        }
    }
    (git_remote, git_user)
}

// Project tox
fn prj_tox(name: &str, venv: bool, deps: &Vec<String>, shortcut: &String) {
    // Check environment variable
    let env_tox = var("PSP_TOX").unwrap_or("false".to_string()).parse().ok();
    let confirm = if let Some(true) = env_tox {
        true
    } else if shortcut == "quick" || shortcut == "simple" {
        false
    } else if shortcut == "full" {
        true
    } else {
        prompt_confirm("Do you want to configure tox?", false, "None")
    };
    // Create tox ini
    if confirm {
        let args = vec![
            "install".to_string(),
            "--timeout=10".to_string(),
            "--retries=1".to_string(),
            "tox".to_string(),
        ];
        let mut pip = make_command(PIP_BIN, name, name, args, venv);
        let output = pip
            .output()
            .expect(format!("{PIP_BIN} should be installed").as_str());
        // Check if command exit successfully
        if !output.status.success() {
            eprintln!("error: `tox` installation failed");
            return;
        }
        // Write tox.ini
        let tox_ini_content = format!(
            "# {SIGNATURE}, version {VERSION}

[tox]
envlist = {}
isolated_build = True

[testenv]
labels = test, core
deps=
    pytest
{}
commands = pytest tests",
            format!("py{}", get_python_version().replace(".", "")),
            deps.iter()
                .map(|s| if s != "No" {
                    format!("\t{s}")
                } else {
                    String::new()
                })
                .collect::<Vec<String>>()
                .join("\n")
        );
        let tox_ini_file = Path::new(name).join("tox.ini");
        let tox_ini = make_file(tox_ini_file.display().to_string().as_str(), tox_ini_content);
        if let Err(e) = tox_ini {
            eprintln!("error: {}", e);
        }
    }
}

// Project documentation site generator
fn prj_docs(root: &str, name: &str, venv: bool, shortcut: &String) {
    let options = vec!["None", "Sphinx", "MKDocs"];
    // Check environment variable
    let env_docs = var("PSP_DOCS").ok();
    let docs = if let Some(env_docs) = env_docs {
        env_docs
    } else if shortcut == "simple" {
        "None".to_string()
    } else {
        prompt_select("Select documentation generator:", options, "None")
    };
    if docs != "None" {
        let docs_home = Path::new(root).join("docs");
        let docs_folder = docs_home.as_path();
        // Check if folder docs exist
        if docs_folder.exists() {
            let folder_result = remove_dir_all(docs_folder);
            if let Err(e) = folder_result {
                eprintln!("error: {}", e);
            }
        }
        // Create docs folder
        let docs_folder = make_dirs(docs_folder.display().to_string().as_str());
        if let Err(e) = docs_folder {
            eprintln!("error: {}", e);
        }
        if docs.as_str().to_lowercase() == "sphinx" {
            // Install sphinx
            let args = vec![
                "install".to_string(),
                "--timeout=10".to_string(),
                "--retries=1".to_string(),
                "sphinx".to_string(),
            ];
            let mut pip = make_command(PIP_BIN, root, root, args, venv);
            let output = pip
                .output()
                .expect(format!("{PIP_BIN} should be installed").as_str());
            // Check if command exit successfully
            if !output.status.success() {
                eprintln!("error: `sphinx` installation failed");
                return;
            }
            // Start documentation
            #[cfg(any(target_os = "linux", target_os = "macos"))]
            let sphinx_bin = "sphinx-quickstart";
            #[cfg(target_os = "windows")]
            let sphinx_bin = "sphinx-quickstart.exe";
            let args = vec![
                "--quiet".to_string(),
                "--sep".to_string(),
                format!("--project={}", name.to_lowercase()),
                "--author=''".to_string(),
                "-v='0.0.1'".to_string(),
                "--ext-autodoc".to_string(),
                "--ext-doctest".to_string(),
                "--ext-viewcode".to_string(),
                "--makefile".to_string(),
                "--quiet".to_string(),
                "--sep".to_string(),
            ];
            let mut sphinx_quickstart = make_command(
                sphinx_bin,
                root,
                docs_home.display().to_string().as_str(),
                args,
                venv,
            );
            let output = sphinx_quickstart
                .output()
                .expect(format!("{sphinx_bin} should be installed").as_str());
            if !output.status.success() {
                eprintln!("error: `sphinx` documentation creation failed");
            }
        } else if docs.as_str().to_lowercase() == "mkdocs" {
            // Install mkdocs
            let args = vec![
                "install".to_string(),
                "--timeout=10".to_string(),
                "--retries=1".to_string(),
                "mkdocs".to_string(),
            ];
            let mut pip = make_command(PIP_BIN, root, root, args, venv);
            let output = pip
                .output()
                .expect(format!("{PIP_BIN} should be installed").as_str());
            // Check if command exit successfully
            if !output.status.success() {
                eprintln!("error: `mkdocs` installation failed");
                return;
            }
            // Start documentation
            #[cfg(any(target_os = "linux", target_os = "macos"))]
            let mkdocs_bin = "mkdocs";
            #[cfg(target_os = "windows")]
            let mkdocs_bin = "mkdocs.exe";
            let args = vec!["new".to_string(), "--quiet".to_string(), ".".to_string()];
            let mut mkdocs_new = make_command(mkdocs_bin, root, root, args, venv);
            let output = mkdocs_new
                .output()
                .expect(format!("{mkdocs_bin} should be installed").as_str());
            // Check if command exit successfully
            if !output.status.success() {
                eprintln!("error: `mkdocs` documentation creation failed");
            }
        } else if docs.as_str().to_lowercase() != "none" {
            println!(
                "warning: `{}` is not recognized as documentation generator",
                docs
            )
        }
        // Link requirements
        let requirements_file = Path::new(root).join("requirements.txt");
        if requirements_file.exists() {
            symlink(requirements_file, docs_home.join("requirements.txt")).ok();
        }
    }
}

// Project common files
fn prj_files(root: &str, name: &str, container: bool, shortcut: &String) {
    // Check environment variable
    let env_files = var("PSP_FILES").unwrap_or("false".to_string()).parse().ok();
    let confirm = if let Some(true) = env_files {
        true
    } else if shortcut == "quick" || shortcut == "full" {
        true
    } else if shortcut == "simple" {
        false
    } else {
        prompt_confirm(
            "Do you want create common files?",
            true,
            "Create README, CONTRIBUTING, CODE_OF_CONDUCT and CHANGES",
        )
    };
    if confirm {
        // Create README
        let mut readme_content = format!(
            "<!-- {SIGNATURE}, version {VERSION} -->

# Welcome to **`{name}`**

> [!CAUTION]
> `{name}` is a Work In Progress (WIP)

## Install {name}
To install `{name}`, follow this:

```console
pip install .
```

## Use {name}
To use `{name}`, follow this:

```python
import {name}

...
```
"
        );
        if container {
            readme_content += format!(
                "
## Use {name} with container (Docker/Podman)
To containerize `{name}`, follow this:

```console
docker build . -t {name}:0.0.1
docker tag {name}:0.0.1 {name}:latest
docker run {name}
```

"
            )
            .as_str();
        }
        readme_content += "## Acknowledgments\n\
        Thanks Python Community!";
        let readme_file = Path::new(root).join("README.md");
        let readme = make_file(readme_file.display().to_string().as_str(), readme_content);
        if let Err(e) = readme {
            eprintln!("error: {}", e);
        }
        // Create CHANGES
        let changes_content = format!(
            "<!-- {SIGNATURE}, version {VERSION} -->

# Changelog

All notable changes to **{name}** will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/),
and this project adheres to [Semantic Versioning](https://semver.org/).

## [Unreleased]

## [0.0.1] - 1970-01-01

### Added
- Start **{name}** project

### Changed
- None

### Removed
- None

### Fixed
- None
"
        );
        let changes_file = Path::new(root).join("CHANGES.md");
        let changes = make_file(changes_file.display().to_string().as_str(), changes_content);
        if let Err(e) = changes {
            eprintln!("error: {}", e);
        }
        // Create CONTRIBUTING
        let contributing_content = format!(
            "<!-- {SIGNATURE}, version {VERSION} -->

# Contributing to {name}

Bug fixes, feature additions, tests, documentation and more can be contributed via issues and/or pull requests.
**All contributions are welcome**.

## Bug fixes, feature additions, etc.

Please send a pull request to the `main` branch.
Please include documentation and tests for new features.
Tests or documentation without bug fixes or feature additions are welcome too.
Feel free to ask questions via issues, discussions, or mail.

- Fork the {name} repository.
- Create a branch from `main`.
- Develop bug fixes, features, tests, etc.
- Run the test suite.
- Create a pull request to pull the changes from your branch to the {name} `main`.

### Guidelines

- Separate code commits from reformatting commits.
- Provide tests for any newly added code.
- Follow PEP 8.
- Include release notes as needed or appropriate with your bug fixes, feature additions and tests.
- Do not add to the changelog for proposed changes, as that is updated after changes are merged.
"
        );
        let contributing_file = Path::new(root).join("CONTRIBUTING.md");
        let contributing = make_file(
            contributing_file.display().to_string().as_str(),
            contributing_content,
        );
        if let Err(e) = contributing {
            eprintln!("error: {}", e);
        }
        // Create CODE_OF_CONDUCT
        let conduct_url =
            "https://www.contributor-covenant.org/version/2/1/code_of_conduct/code_of_conduct.md";
        #[cfg(any(target_os = "linux", target_os = "macos"))]
        let command = "curl";
        #[cfg(target_os = "windows")]
        let command = "powershell.exe";
        #[cfg(any(target_os = "linux", target_os = "macos"))]
        let command_args = vec![
            "-oCODE_OF_CONDUCT.md".to_string(),
            "-k".to_string(),
            "--connect-timeout".to_string(),
            "10".to_string(),
            conduct_url.to_string(),
        ];
        #[cfg(target_os = "windows")]
        let command_args = vec![
            "-NoProfile".to_string(),
            "-ExecutionPolicy".to_string(),
            "Bypass".to_string(),
            "-Command".to_string(),
            "iwr".to_string(),
            "-TimeoutSec".to_string(),
            "10".to_string(),
            "-OutFile".to_string(),
            "CODE_OF_CONDUCT.md".to_string(),
            conduct_url.to_string(),
        ];
        let mut downloader = make_command(command, root, root, command_args, false);
        let output = downloader
            .output()
            .expect(format!("{command} should be installed").as_str());
        // Check if command exit successfully
        if !output.status.success() {
            eprintln!("error: `CODE_OF_CONDUCT` download failed from `{conduct_url}`");
        }
    }
}

// Project license
fn prj_license(name: &str, shortcut: &String) -> String {
    // Select license
    let options = vec![
        "None",
        "MIT",
        "Apache",
        "Mozilla",
        "Creative Commons",
        "Gnu Public License",
    ];
    // Check environment variable
    let env_license = var("PSP_LICENSE").ok();
    let license = if let Some(env_license) = env_license {
        env_license
    } else if shortcut == "simple" {
        "None".to_string()
    } else {
        prompt_select("Select license:", options, "None")
    };
    let mut license_url = String::new();
    let repo_license =
        "https://raw.githubusercontent.com/MatteoGuadrini/psp/refs/heads/main/licenses";
    if license.to_lowercase() == "mit" {
        license_url.push_str(format!("{repo_license}/mit.md").as_str())
    } else if license.to_lowercase() == "apache" {
        license_url.push_str(format!("{repo_license}/apache.md").as_str())
    } else if license.to_lowercase() == "creative commons" || license.to_lowercase() == "cc" {
        license_url.push_str(format!("{repo_license}/cc.md").as_str())
    } else if license.to_lowercase() == "mozilla" {
        license_url.push_str(format!("{repo_license}/mozilla.md").as_str())
    } else if license.to_lowercase() == "gnu public license" || license.to_lowercase() == "gpl" {
        license_url.push_str(format!("{repo_license}/gplv3.md").as_str())
    } else if license.as_str().to_lowercase() != "none" {
        println!(
            "warning: `{}` is not recognized as a valid license",
            license
        )
    }
    if !license_url.is_empty() {
        #[cfg(any(target_os = "linux", target_os = "macos"))]
        let command = "curl";
        #[cfg(target_os = "windows")]
        let command = "powershell.exe";
        #[cfg(any(target_os = "linux", target_os = "macos"))]
        let command_args = vec![
            "-oLICENSE.md".to_string(),
            "-k".to_string(),
            "--connect-timeout".to_string(),
            "10".to_string(),
            license_url.to_string(),
        ];
        #[cfg(target_os = "windows")]
        let command_args = vec![
            "-NoProfile".to_string(),
            "-ExecutionPolicy".to_string(),
            "Bypass".to_string(),
            "-Command".to_string(),
            "iwr".to_string(),
            "-TimeoutSec".to_string(),
            "10".to_string(),
            "-OutFile".to_string(),
            "LICENSE.md".to_string(),
            license_url.to_string(),
        ];
        let mut downloader = make_command(command, name, name, command_args, false);
        let output = downloader
            .output()
            .expect(format!("{command} should be installed").as_str());
        // Check if command exit successfully
        if !output.status.success() {
            eprintln!("error: `LICENSE` download failed from `{license_url}`");
        }
    }
    license
}

// Project pypi dependencies
fn prj_pypi(root: &str, venv: bool, shortcut: &String) -> bool {
    // Check environment variable
    let env_pypi = var("PSP_PYPI").unwrap_or("false".to_string()).parse().ok();
    let confirm = if let Some(true) = env_pypi {
        true
    } else if shortcut == "quick" || shortcut == "full" {
        true
    } else if shortcut == "simple" {
        false
    } else {
        prompt_confirm(
            "Do you want to install dependencies to publish on pypi?",
            true,
            "None",
        )
    };
    if confirm {
        // Install twine and build
        let mut pip = std::process::Command::new(PIP_BIN);
        // Activate venv
        if venv {
            #[cfg(any(target_os = "linux", target_os = "macos"))]
            pip.env(
                "PATH",
                Path::new("venv").join("bin").display().to_string().as_str(),
            );
            #[cfg(target_os = "windows")]
            pip.env(
                "PATH",
                Path::new(absolute(root).unwrap().display().to_string().as_str())
                    .join("venv")
                    .join("Scripts")
                    .display()
                    .to_string()
                    .as_str(),
            );
        }
        let output = pip
            .arg("install")
            .arg("--timeout=10")
            .arg("--retries=1")
            .arg("twine")
            .arg("build")
            .current_dir(&root)
            .output()
            .expect(format!("{PIP_BIN} should be installed").as_str());
        // Check if command exit successfully
        if !output.status.success() {
            eprintln!("error: `build` and `twine` installation failed");
            return false;
        }
        return true;
    }
    false
}

// Project Docker/Podman
fn prj_container(root: &str, name: &str, shortcut: &String) -> bool {
    // Check environment variable
    let env_container = var("PSP_CONTAINER")
        .unwrap_or("false".to_string())
        .parse()
        .ok();
    let confirm = if let Some(true) = env_container {
        true
    } else if shortcut == "quick" || shortcut == "full" {
        true
    } else if shortcut == "simple" {
        false
    } else {
        prompt_confirm(
            "Do you want to create a Dockerfile and Containerfile?",
            true,
            "None",
        )
    };
    if confirm {
        // Create Dockerfile/Containerfile
        let dockerfile_content = format!(
            "# {SIGNATURE}, version {VERSION}

FROM python:alpine

# Update and upgrade packages
RUN apk update && apk upgrade --no-cache

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# Copy the Python package
COPY {name} /{name}/{name}
COPY pyproject.toml /{name}
WORKDIR /{name}

# Install dependencies
RUN pip install . --no-cache-dir

# Safe user
RUN adduser --disabled-password {name}_user
USER {name}_user

# Run package
ENTRYPOINT python
CMD -m {name}
"
        );
        let dockerfile_file = Path::new(root).join("Dockerfile");
        let dockerfile = make_file(
            dockerfile_file.display().to_string().as_str(),
            dockerfile_content.clone(),
        );
        if let Err(e) = dockerfile {
            eprintln!("error: {}", e);
        }
        let containerfile_file = Path::new(root).join("Containerfile");
        let containerfile = make_file(
            containerfile_file.display().to_string().as_str(),
            dockerfile_content,
        );
        if let Err(e) = containerfile {
            eprintln!("error: {}", e);
        }
        // Create .dockerignore/.containerignore
        let docker_ignore_content = format!(
            "# {SIGNATURE}, version {VERSION}

__pycache__
*.pyc
.git
.pytest_cache
.venv
.env
venv
env
README.md
"
        );
        let dockerignore_file = Path::new(root).join(".dockerignore");
        let dockerignore = make_file(
            dockerignore_file.display().to_string().as_str(),
            docker_ignore_content.clone(),
        );
        if let Err(e) = dockerignore {
            eprintln!("error: {}", e);
        }
        let containerignore_file = Path::new(root).join(".containerignore");
        let containerignore = make_file(
            containerignore_file.display().to_string().as_str(),
            docker_ignore_content,
        );
        if let Err(e) = containerignore {
            eprintln!("error: {}", e);
        }
        true
    } else {
        false
    }
}

// Project Makefile
fn prj_makefile(root: &str, name: &str, tests: bool, build: bool, container: bool) {
    // Set options for make
    let mut make_options = vec!["help", "all", "run", "clean"];
    if tests {
        make_options.push("test");
    }
    if build {
        make_options.push("build");
        make_options.push("deploy");
    }
    if container {
        make_options.push("container");
    }
    let mut makefile_content = format!(
        "# {SIGNATURE}, version {VERSION}

PYTHON = {}
VENV = venv

.PHONY: {}

help:
\t@echo 'help: make ({})'
",
        PYTHON_BIN,
        make_options.join(" "),
        make_options.join("|")
    );
    // Add all target
    make_options.remove(0);
    make_options.remove(0);
    if make_options[2] == "test" {
        let removed_test = make_options.remove(2);
        make_options.insert(0, removed_test);
    }
    makefile_content += format!(
        "
all: {}
",
        make_options.join(" ")
    )
    .as_str();
    // Check if tests variable has been specified
    if tests {
        makefile_content += format!(
            "
test:
ifneq ('$(wildcard ${{VENV}}/bin/${{PYTHON}})','')
\t${{VENV}}/bin/${{PYTHON}} -m unittest
else
\t${{PYTHON}} -m unittest
endif
"
        )
        .as_str();
    }
    // Check if build variable has been specified
    if build {
        makefile_content += format!(
            "
build:
ifneq ('$(wildcard ${{VENV}}/bin/${{PYTHON}})','')
\t${{VENV}}/bin/${{PYTHON}} -m build
else
\t${{PYTHON}} -m build
endif

deploy:
ifneq ('$(wildcard ${{VENV}}/bin/${{PYTHON}})','')
\t${{VENV}}/bin/${{PYTHON}} -m twine upload dist/*
else
\t${{PYTHON}} -m twine upload dist/*
endif
"
        )
        .as_str();
    }
    // Check if container variable has been specified
    if container {
        makefile_content += format!(
            "
container:
\tpodman build . -t {name}:latest || docker build . -t {name}:latest
"
        )
        .as_str();
    }
    makefile_content += format!(
        "
run:
ifneq ('$(wildcard ${{VENV}}/bin/${{PYTHON}})','')
\t${{VENV}}/bin/${{PYTHON}} -m {name}
else
\t${{PYTHON}} -m {name}
endif

clean:
\trm -fr build/
\trm -fr dist/
\trm -fr .eggs/
\tfind . -name '*.egg-info' -exec rm -fr {{}} +
\tfind . -name '*.egg' -exec rm -f {{}} +
\tfind . -name '*.pyc' -exec rm -f {{}} +
\tfind . -name '*.pyo' -exec rm -f {{}} +
\tfind . -name '*~' -exec rm -f {{}} +
\tfind . -name '__pycache__' -exec rm -fr {{}} +
"
    )
    .as_str();
    let makefile_file = Path::new(root).join("Makefile");
    let makefile = make_file(
        makefile_file.display().to_string().as_str(),
        makefile_content,
    );
    if let Err(e) = makefile {
        eprintln!("error: {}", e);
    }
}

// Main program
fn main() {
    // Load env files
    load_env();
    // Check if argument is specified
    let shortcut = get_shortcut();
    // Print help message
    if shortcut == "help" {
        print_help(0)
    }
    // Print welcome screen and version
    println!("info: welcome to psp, version {VERSION}");
    for tool in TOOLS {
        check_tool(tool);
    }
    // Create project structure by name or path
    let (root, name) = prj_name();
    // Virtual Environment
    let venv = prj_venv(&root, &shortcut);
    // Start git
    let git = prj_git(&root, &shortcut);
    // Git remote
    let git_info = if git {
        prj_remote(&root, &name, &shortcut)
    } else {
        ("None".to_string(), "None".to_string())
    };
    // Unit tests
    let tests = prj_test(&root, &name, &shortcut);
    // Install dependencies
    let deps = prj_deps(&root, venv, &shortcut);
    // Documentation
    prj_docs(&root, &name, venv, &shortcut);
    if tests {
        // Tox
        prj_tox(&root, venv, &deps, &shortcut);
        // CI configuration
        prj_ci(&root, &deps, &shortcut);
    }
    // License
    let license = prj_license(&root, &shortcut);
    // Build dependencies
    let build = prj_pypi(&root, venv, &shortcut);
    // Write pyproject.toml
    prj_toml(&root, &name, &deps, git_info, license);
    // Dockerfile
    let container = prj_container(&root, &name, &shortcut);
    // Common files
    prj_files(&root, &name, container, &shortcut);
    // Makefile
    prj_makefile(&root, &name, tests, build, container);
    // Finish a scaffolding process
    println!(
        "info: python project `{name}` created at `{}`",
        absolute(root).unwrap().display()
    )
}
