//
//  Definition.swift
//
//
//  Created by Thibaut Schmitt on 04/01/2022.
//

import Foundation

// swiftlint:disable force_unwrapping

class Definition {

    // MARK: - Properties

    let name: String
    var tags = [String]()
    var comment: String?
    var translations = [String: String]()
    var reference: String?
    var isPlurals = false

    var isValid: Bool {
        name.isEmpty == false &&
        translations.isEmpty == false
    }

    init(name: String) {
        self.name = name
    }

    static func match(_ line: String) -> Definition? {
        guard line.range(of: "\\[(.*?)]$", options: .regularExpression, range: nil, locale: nil) != nil else {
            return nil
        }

        let definitionName = line
            .replacingOccurrences(of: ["[", "]"], with: "")
            .removeLeadingTrailingWhitespace()

        return Definition(name: definitionName)
    }

    func hasOneOrMoreMatchingTags(inputTags: [String]) -> Bool {
        if Set(inputTags).isDisjoint(with: tags) {
            return false
        }
        return true
    }

    // MARK: -

    private func getStringParameters(input: String) -> (inputParameters: [String], translationArguments: [String])? {
        var methodsParameters = [String]()

        let printfPlaceholderRegex = try! NSRegularExpression( // swiftlint:disable:this force_try
            pattern: "%(?:\\d+\\$)?[+-]?(?:[ 0]|'.{1})?-?\\d*(?:\\.\\d+)?[blcdeEufFgGosxX@]*"
        )
        printfPlaceholderRegex.enumerateMatches(
            in: input,
            options: [],
            range: NSRange(location: 0, length: input.count)
        ) { match, _, stop in // swiftlint:disable:this unused_closure_parameter
            guard let match else { return }

            if let range = Range(match.range, in: input), let last = input[range].last {
                switch last {
                case "d", "u":
                    methodsParameters.append("Int")

                case "f", "F":
                    methodsParameters.append("Double")

                case "@", "s", "c":
                    methodsParameters.append("String")

                case "%":
                    // if you need to print %, you have to add %%
                    break

                default:
                    break
                }
            }
        }

        if methodsParameters.isEmpty {
            return nil
        }

        var inputParameters = [String]()
        var translationArguments = [String]()
        for (index, paramType) in methodsParameters.enumerated() {
            let paramName = "arg\(index)"
            translationArguments.append(paramName)
            inputParameters.append("\(paramName): \(paramType)")
        }

        return (inputParameters: inputParameters, translationArguments: translationArguments)
    }

    private func getBaseProperty(lang: String, translation: String, isStatic: Bool, comment: String?) -> String {
        """
            /// Translation in \(lang) :
            /// \(translation)
            ///
            /// Comment :
            /// \(comment?.isEmpty == false ? comment! : "No comment")
            \(isStatic ? "static " : "")var \(name): String {
                NSLocalizedString("\(name)", tableName: kStringsFileName, bundle: Bundle.main, value: "\(translation)", comment: "\(comment ?? "")")
            }
        """
    }

    private func getBaseMethod(
        lang: String,
        translation: String,
        isStatic: Bool,
        inputParameters: [String],
        translationArguments: [String],
        comment: String?
    ) -> String {
        """

            /// Translation in \(lang) :
            /// \(translation)
            ///
            /// Comment :
            /// \(comment?.isEmpty == false ? comment! : "No comment")
            \(isStatic ? "static " : "")func \(name)(\(inputParameters.joined(separator: ", "))) -> String {
                String(format: \(isStatic ? "Self" : "self").\(name), \(translationArguments.joined(separator: ", ")))
            }
        """
    }

    func getNSLocalizedStringProperty(forLang lang: String) -> String {
        guard let translation = translations[lang] else {
            let error = StringiumError.langNotDefined(lang, name, reference != nil)
            print(error.description)
            Stringium.exit(withError: error)
        }

        // Generate property
        let property = getBaseProperty(
            lang: lang,
            translation: translation,
            isStatic: false,
            comment: self.comment
        )

        // Generate method
        var method = ""
        if let parameters = self.getStringParameters(input: translation) {
            method = getBaseMethod(
                lang: lang,
                translation: translation,
                isStatic: false,
                inputParameters: parameters.inputParameters,
                translationArguments: parameters.translationArguments,
                comment: self.comment
            )
        }

        return property + method
    }

    func getNSLocalizedStringStaticProperty(forLang lang: String) -> String {
        guard let translation = translations[lang] else {
            let error = StringiumError.langNotDefined(lang, name, reference != nil)
            print(error.description)
            Stringium.exit(withError: error)
        }

        // Generate property
        let property = getBaseProperty(
            lang: lang,
            translation: translation,
            isStatic: true,
            comment: self.comment
        )

        // Generate method
        var method = ""
        if let parameters = self.getStringParameters(input: translation) {
            method = getBaseMethod(
                lang: lang,
                translation: translation,
                isStatic: true,
                inputParameters: parameters.inputParameters,
                translationArguments: parameters.translationArguments,
                comment: self.comment
            )
        }

        return property + method
    }

    // MARK: - Raw strings

    func getProperty(forLang lang: String) -> String {
        guard let translation = translations[lang] else {
            let error = StringiumError.langNotDefined(lang, name, reference != nil)
            print(error.description)
            Stringium.exit(withError: error)
        }

        return """
            /// Translation in \(lang) :
            /// \(translation)
            ///
            /// Comment :
            /// \(comment?.isEmpty == false ? comment! : "No comment")

            var \(name): String {
                "\(translation)"
            }
        """
    }

    func getStaticProperty(forLang lang: String) -> String {
        guard let translation = translations[lang] else {
            let error = StringiumError.langNotDefined(lang, name, reference != nil)
            print(error.description)
            Stringium.exit(withError: error)
        }

        return """
            /// Translation in \(lang) :
            /// \(translation)
            ///
            /// Comment :
            /// \(comment?.isEmpty == false ? comment! : "No comment")
            static var \(name): String {
                "\(translation)"
            }
        """
    }
}
