// Copyright (c) 2017-2026, University of Cincinnati, developed by Henry Schreiner
// under NSF AWARD 1414736 and by the respective contributors.
// All rights reserved.
//
// SPDX-License-Identifier: BSD-3-Clause

#pragma once

// IWYU pragma: private, include "CLI/CLI.hpp"
#include "../Validators.hpp"

#include "../Encoding.hpp"
#include "../Macros.hpp"
#include "../StringTools.hpp"
#include "../TypeTools.hpp"

// [CLI11:public_includes:set]
#include <functional>
#include <stdexcept>
#include <string>
#include <system_error>
#include <utility>
// [CLI11:public_includes:end]

namespace CLI {
// [CLI11:validators_inl_hpp:verbatim]

CLI11_INLINE Validator::Validator(std::string validator_desc, std::function<std::string(std::string &)> func)
    : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(func)) {}

CLI11_INLINE Validator::Validator(std::function<std::string(std::string &)> op,
                                  std::string validator_desc,
                                  std::string validator_name)
    : desc_function_([validator_desc]() { return validator_desc; }), func_(std::move(op)),
      name_(std::move(validator_name)) {}

CLI11_INLINE std::string Validator::operator()(std::string &str) const {
    std::string retstring;
    if(active_) {
        if(non_modifying_) {
            std::string value = str;
            retstring = func_(value);
        } else {
            retstring = func_(str);
        }
    }
    return retstring;
}

CLI11_INLINE std::string Validator::operator()(const std::string &str) const {
    std::string value = str;
    return (active_) ? func_(value) : std::string{};
}

CLI11_NODISCARD CLI11_INLINE Validator Validator::description(std::string validator_desc) const {
    Validator newval(*this);
    newval.desc_function_ = [validator_desc]() { return validator_desc; };
    return newval;
}

CLI11_NODISCARD CLI11_INLINE std::string Validator::get_description() const {
    if(active_) {
        return desc_function_();
    }
    return std::string{};
}

CLI11_NODISCARD CLI11_INLINE Validator Validator::name(std::string validator_name) const {
    Validator newval(*this);
    newval.name_ = std::move(validator_name);
    return newval;
}

CLI11_NODISCARD CLI11_INLINE Validator Validator::active(bool active_val) const {
    Validator newval(*this);
    newval.active_ = active_val;
    return newval;
}

CLI11_NODISCARD CLI11_INLINE Validator Validator::application_index(int app_index) const {
    Validator newval(*this);
    newval.application_index_ = app_index;
    return newval;
}

CLI11_INLINE Validator Validator::operator&(const Validator &other) const {
    Validator newval;

    newval._merge_description(*this, other, " AND ");

    // Give references (will make a copy in lambda function)
    const std::function<std::string(std::string & filename)> &f1 = func_;
    const std::function<std::string(std::string & filename)> &f2 = other.func_;

    newval.func_ = [f1, f2](std::string &input) {
        std::string s1 = f1(input);
        std::string s2 = f2(input);
        if(!s1.empty() && !s2.empty())
            return std::string("(") + s1 + ") AND (" + s2 + ")";
        return s1 + s2;
    };

    newval.active_ = active_ && other.active_;
    newval.application_index_ = application_index_;
    return newval;
}

CLI11_INLINE Validator Validator::operator|(const Validator &other) const {
    Validator newval;

    newval._merge_description(*this, other, " OR ");

    // Give references (will make a copy in lambda function)
    const std::function<std::string(std::string &)> &f1 = func_;
    const std::function<std::string(std::string &)> &f2 = other.func_;

    newval.func_ = [f1, f2](std::string &input) {
        std::string s1 = f1(input);
        std::string s2 = f2(input);
        if(s1.empty() || s2.empty())
            return std::string();

        return std::string("(") + s1 + ") OR (" + s2 + ")";
    };
    newval.active_ = active_ && other.active_;
    newval.application_index_ = application_index_;
    return newval;
}

CLI11_INLINE Validator Validator::operator!() const {
    Validator newval;
    const std::function<std::string()> &dfunc1 = desc_function_;
    newval.desc_function_ = [dfunc1]() {
        auto str = dfunc1();
        return (!str.empty()) ? std::string("NOT ") + str : std::string{};
    };
    // Give references (will make a copy in lambda function)
    const std::function<std::string(std::string & res)> &f1 = func_;

    newval.func_ = [f1, dfunc1](std::string &test) -> std::string {
        std::string s1 = f1(test);
        if(s1.empty()) {
            return std::string("check ") + dfunc1() + " succeeded improperly";
        }
        return std::string{};
    };
    newval.active_ = active_;
    newval.application_index_ = application_index_;
    return newval;
}

CLI11_INLINE void
Validator::_merge_description(const Validator &val1, const Validator &val2, const std::string &merger) {

    const std::function<std::string()> &dfunc1 = val1.desc_function_;
    const std::function<std::string()> &dfunc2 = val2.desc_function_;

    desc_function_ = [=]() {
        std::string f1 = dfunc1();
        std::string f2 = dfunc2();
        if((f1.empty()) || (f2.empty())) {
            return f1 + f2;
        }
        return std::string(1, '(') + f1 + ')' + merger + '(' + f2 + ')';
    };
}

namespace detail {

#if defined CLI11_HAS_FILESYSTEM && CLI11_HAS_FILESYSTEM > 0
CLI11_INLINE path_type check_path(const char *file) noexcept {
    std::error_code ec;
    auto stat = std::filesystem::status(to_path(file), ec);
    if(ec) {
        return path_type::nonexistent;
    }
    switch(stat.type()) {
    case std::filesystem::file_type::none:  // LCOV_EXCL_LINE
    case std::filesystem::file_type::not_found:
        return path_type::nonexistent;  // LCOV_EXCL_LINE
    case std::filesystem::file_type::directory:
        return path_type::directory;
    case std::filesystem::file_type::symlink:
    case std::filesystem::file_type::block:
    case std::filesystem::file_type::character:
    case std::filesystem::file_type::fifo:
    case std::filesystem::file_type::socket:
    case std::filesystem::file_type::regular:
    case std::filesystem::file_type::unknown:
    default:
        return path_type::file;
    }
}
#else
CLI11_INLINE path_type check_path(const char *file) noexcept {
#if defined(_MSC_VER)
    struct __stat64 buffer;
    if(_stat64(file, &buffer) == 0) {
        return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file;
    }
#else
    struct stat buffer;
    if(stat(file, &buffer) == 0) {
        return ((buffer.st_mode & S_IFDIR) != 0) ? path_type::directory : path_type::file;
    }
#endif
    return path_type::nonexistent;
}
#endif

CLI11_INLINE ExistingFileValidator::ExistingFileValidator() : Validator("FILE") {
    func_ = [](std::string &filename) -> std::string {
        if(filename.empty())
            return std::string("File name is missing.");

        auto path_result = check_path(filename.c_str());
        if(path_result == path_type::nonexistent) {
            return "File does not exist: " + filename;
        }
        if(path_result == path_type::directory) {
            return "File is actually a directory: " + filename;
        }
        return std::string();
    };
}

CLI11_INLINE ExistingDirectoryValidator::ExistingDirectoryValidator() : Validator("DIR") {
    func_ = [](std::string &filename) {
        auto path_result = check_path(filename.c_str());
        if(path_result == path_type::nonexistent) {
            return "Directory does not exist: " + filename;
        }
        if(path_result == path_type::file) {
            return "Directory is actually a file: " + filename;
        }
        return std::string();
    };
}

CLI11_INLINE ExistingPathValidator::ExistingPathValidator() : Validator("PATH(existing)") {
    func_ = [](std::string &filename) {
        auto path_result = check_path(filename.c_str());
        if(path_result == path_type::nonexistent) {
            return "Path does not exist: " + filename;
        }
        return std::string();
    };
}

CLI11_INLINE NonexistentPathValidator::NonexistentPathValidator() : Validator("PATH(non-existing)") {
    func_ = [](std::string &filename) {
        auto path_result = check_path(filename.c_str());
        if(path_result != path_type::nonexistent) {
            return "Path already exists: " + filename;
        }
        return std::string();
    };
}

CLI11_INLINE EscapedStringTransformer::EscapedStringTransformer() {
    func_ = [](std::string &str) {
        try {
            if(str.size() > 1 && (str.front() == '\"' || str.front() == '\'' || str.front() == '`') &&
               str.front() == str.back()) {
                process_quoted_string(str);
            } else if(str.find_first_of('\\') != std::string::npos) {
                if(detail::is_binary_escaped_string(str)) {
                    str = detail::extract_binary_string(str);
                } else {
                    str = remove_escaped_characters(str);
                }
            }
            return std::string{};
        } catch(const std::invalid_argument &ia) {
            return std::string(ia.what());
        }
    };
}
}  // namespace detail

CLI11_INLINE FileOnDefaultPath::FileOnDefaultPath(std::string default_path, bool enableErrorReturn)
    : Validator("FILE") {
    func_ = [default_path, enableErrorReturn](std::string &filename) {
        auto path_result = detail::check_path(filename.c_str());
        if(path_result == detail::path_type::nonexistent) {
            std::string test_file_path = default_path;
            if(!default_path.empty() && default_path.back() != '/' && default_path.back() != '\\') {
                // Add folder separator
                test_file_path += '/';
            }
            test_file_path.append(filename);
            path_result = detail::check_path(test_file_path.c_str());
            if(path_result == detail::path_type::file) {
                filename = test_file_path;
            } else {
                if(enableErrorReturn) {
                    return "File does not exist: " + filename;
                }
            }
        }
        return std::string{};
    };
}

namespace detail {

CLI11_INLINE std::pair<std::string, std::string> split_program_name(std::string commandline) {
    // try to determine the programName
    std::pair<std::string, std::string> vals;
    trim(commandline);
    auto esp = commandline.find_first_of(' ', 1);
    while(detail::check_path(commandline.substr(0, esp).c_str()) != path_type::file) {
        if(esp == std::string::npos) {
            // if we have reached the end and haven't found a valid file just assume the first argument is the
            // program name
            if(commandline[0] == '"' || commandline[0] == '\'' || commandline[0] == '`') {
                bool embeddedQuote = false;
                auto keyChar = commandline[0];
                auto end = commandline.find_first_of(keyChar, 1);
                while((end != std::string::npos) && (commandline[end - 1] == '\\')) {  // deal with escaped quotes
                    end = commandline.find_first_of(keyChar, end + 1);
                    embeddedQuote = true;
                }
                if(end != std::string::npos) {
                    vals.first = commandline.substr(1, end - 1);
                    esp = end + 1;
                    if(embeddedQuote) {
                        vals.first = find_and_replace(vals.first, std::string("\\") + keyChar, std::string(1, keyChar));
                    }
                } else {
                    esp = commandline.find_first_of(' ', 1);
                }
            } else {
                esp = commandline.find_first_of(' ', 1);
            }

            break;
        }
        esp = commandline.find_first_of(' ', esp + 1);
    }
    if(vals.first.empty()) {
        vals.first = commandline.substr(0, esp);
        rtrim(vals.first);
    }

    // strip the program name
    vals.second = (esp < commandline.length() - 1) ? commandline.substr(esp + 1) : std::string{};
    ltrim(vals.second);
    return vals;
}

}  // namespace detail

// [CLI11:validators_inl_hpp:end]
}  // namespace CLI
