"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.NativeAddSwiftCommand = exports.NativeAddObjectiveCCommand = exports.NativeAddKotlinCommand = exports.NativeAddJavaCommand = exports.NativeAddAndroidCommand = exports.NativeAddSingleCommand = exports.NativeAddCommand = void 0;
const fs = require("fs");
const path = require("path");
const yok_1 = require("../common/yok");
const utils_1 = require("../common/utils");
const os_1 = require("os");
class NativeAddCommand {
    constructor($projectData, $logger, $errors) {
        this.$projectData = $projectData;
        this.$logger = $logger;
        this.$errors = $errors;
        this.allowedParameters = [];
        this.$projectData.initializeProjectData();
    }
    execute(args) {
        return __awaiter(this, void 0, void 0, function* () {
            this.failWithUsage();
            return Promise.resolve();
        });
    }
    failWithUsage() {
        this.$errors.failWithHelp("Usage: ns native add [swift|objective-c|java|kotlin] [class name]");
    }
    canExecute(args) {
        return __awaiter(this, void 0, void 0, function* () {
            this.failWithUsage();
            return false;
        });
    }
    getIosSourcePathBase() {
        const resources = this.$projectData.getAppResourcesDirectoryPath();
        return path.join(resources, "iOS", "src");
    }
    getAndroidSourcePathBase() {
        const resources = this.$projectData.getAppResourcesDirectoryPath();
        return path.join(resources, "Android", "src", "main", "java");
    }
}
exports.NativeAddCommand = NativeAddCommand;
class NativeAddSingleCommand extends NativeAddCommand {
    constructor($projectData, $logger, $errors) {
        super($projectData, $logger, $errors);
    }
    canExecute(args) {
        return __awaiter(this, void 0, void 0, function* () {
            if (!args || args.length !== 1) {
                this.failWithUsage();
            }
            return true;
        });
    }
}
exports.NativeAddSingleCommand = NativeAddSingleCommand;
class NativeAddAndroidCommand extends NativeAddSingleCommand {
    constructor($projectData, $logger, $errors) {
        super($projectData, $logger, $errors);
    }
    getPackageName(className) {
        const lastDotIndex = className.lastIndexOf(".");
        if (lastDotIndex !== -1) {
            return className.substring(0, lastDotIndex);
        }
        return "";
    }
    getClassSimpleName(className) {
        const lastDotIndex = className.lastIndexOf(".");
        if (lastDotIndex !== -1) {
            return className.substring(lastDotIndex + 1);
        }
        return className;
    }
    generateJavaClassContent(packageName, classSimpleName) {
        return ((packageName.length > 0 ? `package ${packageName};` : "") +
            `
import android.util.Log;

public class ${classSimpleName} {
    public void logMessage() {
        Log.d("JS", "Hello from ${classSimpleName}!");
    }
}
`);
    }
    generateKotlinClassContent(packageName, classSimpleName) {
        return ((packageName.length > 0 ? `package ${packageName};` : "") +
            `

import android.util.Log

class ${classSimpleName} {
    fun logMessage() {
        Log.d("JS", "Hello from ${classSimpleName}!")
    }
}
`);
    }
    doJavaKotlin(className, extension) {
        const fileExt = extension == "java" ? extension : "kt";
        const packageName = this.getPackageName(className);
        const classSimpleName = this.getClassSimpleName(className);
        const packagePath = path.join(this.getAndroidSourcePathBase(), ...packageName.split("."));
        const filePath = path.join(packagePath, `${classSimpleName}.${fileExt}`);
        if (fs.existsSync(filePath)) {
            this.$errors.failWithHelp(`${extension} file '${filePath}' already exists.`);
            return;
        }
        if (extension == "kotlin" && !this.checkAndUpdateGradleProperties()) {
            return;
        }
        const fileContent = extension == "java"
            ? this.generateJavaClassContent(packageName, classSimpleName)
            : this.generateKotlinClassContent(packageName, classSimpleName);
        fs.mkdirSync(packagePath, { recursive: true });
        fs.writeFileSync(filePath, fileContent);
        this.$logger.info(`${(0, utils_1.capitalizeFirstLetter)(extension)} file '${filePath}' generated successfully.`);
    }
    checkAndUpdateGradleProperties() {
        const resources = this.$projectData.getAppResourcesDirectoryPath();
        const filePath = path.join(resources, "Android", "gradle.properties");
        if (fs.existsSync(filePath)) {
            const fileContent = fs.readFileSync(filePath, "utf8");
            const propertyRegex = /^useKotlin\s*=\s*(true|false)$/m;
            const match = propertyRegex.exec(fileContent);
            if (match) {
                const useKotlin = match[1];
                if (useKotlin === "false") {
                    this.$errors.failWithHelp("The useKotlin property is set to false. Stopping processing. Kotlin must be enabled in gradle.properties to use.");
                    return false;
                }
                if (useKotlin === "true") {
                    return true;
                }
            }
            else {
                fs.appendFileSync(filePath, `${os_1.EOL}useKotlin=true${os_1.EOL}`);
                this.$logger.info('Added "useKotlin=true" property to gradle.properties.');
            }
        }
        else {
            fs.writeFileSync(filePath, `useKotlin=true${os_1.EOL}`);
            this.$logger.info('Created gradle.properties with "useKotlin=true" property.');
        }
        return true;
    }
}
exports.NativeAddAndroidCommand = NativeAddAndroidCommand;
class NativeAddJavaCommand extends NativeAddAndroidCommand {
    constructor($projectData, $logger, $errors) {
        super($projectData, $logger, $errors);
    }
    execute(args) {
        return __awaiter(this, void 0, void 0, function* () {
            this.doJavaKotlin(args[0], "java");
            return Promise.resolve();
        });
    }
}
exports.NativeAddJavaCommand = NativeAddJavaCommand;
class NativeAddKotlinCommand extends NativeAddAndroidCommand {
    constructor($projectData, $logger, $errors) {
        super($projectData, $logger, $errors);
    }
    execute(args) {
        return __awaiter(this, void 0, void 0, function* () {
            this.doJavaKotlin(args[0], "kotlin");
            return Promise.resolve();
        });
    }
}
exports.NativeAddKotlinCommand = NativeAddKotlinCommand;
class NativeAddObjectiveCCommand extends NativeAddSingleCommand {
    constructor($projectData, $logger, $errors) {
        super($projectData, $logger, $errors);
    }
    execute(args) {
        return __awaiter(this, void 0, void 0, function* () {
            this.doObjectiveC(args[0]);
            return Promise.resolve();
        });
    }
    doObjectiveC(className) {
        const iosSourceBase = this.getIosSourcePathBase();
        const classFilePath = path.join(iosSourceBase, `${className}.m`);
        const headerFilePath = path.join(iosSourceBase, `${className}.h`);
        if (this.generateObjectiveCFiles(className, classFilePath, headerFilePath)) {
            // Modify/Generate moduleMap
            this.generateOrUpdateModuleMap(`${className}.h`, path.join(iosSourceBase, "module.modulemap"));
        }
    }
    generateOrUpdateModuleMap(headerFileName, moduleMapPath) {
        const moduleName = "LocalModule";
        const headerPath = headerFileName;
        let moduleMapContent = "";
        if (fs.existsSync(moduleMapPath)) {
            moduleMapContent = fs.readFileSync(moduleMapPath, "utf8");
        }
        const headerDeclaration = `header "${headerPath}"`;
        if (moduleMapContent.includes(`module ${moduleName}`)) {
            // Module declaration already exists in the module map
            if (moduleMapContent.includes(headerDeclaration)) {
                // Header is already present in the module map
                this.$logger.warn(`Header '${headerFileName}' is already added to the module map.`);
                return;
            }
            const updatedModuleMapContent = moduleMapContent.replace(new RegExp(`module ${moduleName} {\\s*([^}]*)\\s*}`, "s"), `module ${moduleName} {${os_1.EOL}    $1${os_1.EOL}    ${headerDeclaration}${os_1.EOL}}`);
            fs.writeFileSync(moduleMapPath, updatedModuleMapContent);
        }
        else {
            // Module declaration does not exist in the module map
            const moduleDeclaration = `module ${moduleName} {${os_1.EOL}    ${headerDeclaration}${os_1.EOL}    export *${os_1.EOL}}`;
            moduleMapContent += `${os_1.EOL}${os_1.EOL}${moduleDeclaration}`;
            fs.writeFileSync(moduleMapPath, moduleMapContent);
        }
        this.$logger.info(`Module map '${moduleMapPath}' has been updated with the header '${headerFileName}'.`);
    }
    generateObjectiveCFiles(className, classFilePath, interfaceFilePath) {
        if (fs.existsSync(classFilePath)) {
            this.$errors.failWithHelp(`Error: File '${classFilePath}' already exists.`);
            return false;
        }
        if (fs.existsSync(interfaceFilePath)) {
            this.$errors.failWithHelp(`Error: File '${interfaceFilePath}' already exists.`);
            return false;
        }
        const interfaceContent = `#import <Foundation/Foundation.h>

@interface ${className} : NSObject

- (void)logMessage;

@end
`;
        const classContent = `#import "${className}.h"

@implementation ${className}

- (void)logMessage {
    NSLog(@"Hello from ${className} class!");
}

@end
`;
        fs.writeFileSync(classFilePath, classContent);
        this.$logger.trace(`Objective-C class file '${classFilePath}' generated successfully.`);
        fs.writeFileSync(interfaceFilePath, interfaceContent);
        this.$logger.trace(`Objective-C interface file '${interfaceFilePath}' generated successfully.`);
        return true;
    }
}
exports.NativeAddObjectiveCCommand = NativeAddObjectiveCCommand;
class NativeAddSwiftCommand extends NativeAddSingleCommand {
    constructor($projectData, $logger, $errors) {
        super($projectData, $logger, $errors);
    }
    execute(args) {
        return __awaiter(this, void 0, void 0, function* () {
            this.doSwift(args[0]);
            return Promise.resolve();
        });
    }
    doSwift(className) {
        const iosSourceBase = this.getIosSourcePathBase();
        const swiftFilePath = path.join(iosSourceBase, `${className}.swift`);
        this.generateSwiftFile(className, swiftFilePath);
    }
    generateSwiftFile(className, filePath) {
        const directory = path.dirname(filePath);
        if (!fs.existsSync(directory)) {
            fs.mkdirSync(directory, { recursive: true });
            this.$logger.trace(`Created directory: '${directory}'.`);
        }
        if (fs.existsSync(filePath)) {
            this.$errors.failWithHelp(`Error: File '${filePath}' already exists.`);
            return;
        }
        const content = `import Foundation;
import os;		

@objc class ${className}: NSObject {
	@objc func logMessage() {
		os_log("Hello from ${className} class!")
	}
}`;
        fs.writeFileSync(filePath, content);
        this.$logger.info(`Swift file '${filePath}' generated successfully.`);
    }
}
exports.NativeAddSwiftCommand = NativeAddSwiftCommand;
yok_1.injector.registerCommand(["native|add"], NativeAddCommand);
yok_1.injector.registerCommand(["native|add|java"], NativeAddJavaCommand);
yok_1.injector.registerCommand(["native|add|kotlin"], NativeAddKotlinCommand);
yok_1.injector.registerCommand(["native|add|swift"], NativeAddSwiftCommand);
yok_1.injector.registerCommand(["native|add|objective-c"], NativeAddObjectiveCCommand);
//# sourceMappingURL=native-add.js.map