import os from 'os';
import path from 'path';
import axios from 'axios';
import {createReadStream} from 'node:fs';
import {mkdir, readdir, stat, readFile, writeFile, rm, unlink} from 'node:fs/promises';
import {sha256} from 'js-sha256';
import {npmInstall, podInstall} from './projectSetup';
import {FileSystem} from '../ui/src/types';
import {rooms, sendLog} from '../websocket';
import { 
  codegenIntegrations, 
  compileMultiplePlugins 
} from './compiler';
import {
  exec, 
  createTargetName, 
  makeHeadersWithCookie
} from '../utils';
import {
  getWorkspaces, 
  getWorkspaceByLocation, 
  createWorkspace, 
  getWorkspace, 
  getOpenApp
} from '../database/cliconfig';
import { logger } from '../utils/logger';
import * as fs from 'fs/promises';

const homeDir = os.homedir();

export async function openWithFinder(location: string) {
  const command = `open -R ${location}`;
  console.log("Executing: " + command)
  await exec(command)
}

export async function openWithCode(location: string) {
  const command = `code ${location}`;
  console.log("Executing: " + command)
  await exec(command);
}

type AppManifest = {
  name: string;
  uuid: string;
  gitRepo: string|null;
};

type AppIntegration = {
  integrationCode: string;
  excerpt: string; // gitRepo
};

type InfoFromApptileIO = {
  apptileServer: string;
  appconfigServer: string;
};

export async function generateApptileConfig(existingConfig: ProjectConfig) {
  type Assets = Array<{ assetClass: string; url: string }>;
  const appId = existingConfig.APP_ID;
  const openedApp = await getOpenApp(appId);

  try {
    if (openedApp) {
      // TODO(gaurav) replace this with actual api call

      let appconfig = {data: {}};
      if (process.env.NODE_ENV === "production") {
        appconfig = (await axios.get(
          `${existingConfig.APPTILE_BUILD_MANAGER_URL}/build-manager/api/build/${appId}/buildConfig`,
          {
            headers: { "x-app-id": appId },
          }
        )).data;
      } else {
        if (openedApp.apptilecookie) {
          const headers = makeHeadersWithCookie(openedApp.apptilecookie, {
            "x-app-id": appId,
          });
          appconfig = (await axios.get(
            `${existingConfig.APPTILE_BACKEND_URL}/build-system/api/build/${appId}/buildConfig`,
            headers
          )).data;
        } else {
          console.error("Cookie not set");
          return;
        }
      }
      /*
      const appconfig = {"apptile_api_endpoint":"http://apptile-server:3000","analytics_api_endpoint":"https://staging-analytics.apptile.io","apptile_update_endpoint":"https://dev-appconfigs.apptile.io","app_id":"100d9050-7c76-4427-8221-ed3a3a1b4163","app_name":"test","url_scheme":"dsadassda","app_host":"daadsasaw","app_host_2":"dwadasdasadsads","build_ios":true,"build_android":true,"ios":{"icon_path":"100d9050-7c76-4427-8221-ed3a3a1b4163/icon/f766ec42-3518-44a2-bb0a-c8d3e76c32d9/icon.png","splash_path":"100d9050-7c76-4427-8221-ed3a3a1b4163/splash/52f30302-0df9-47a5-9cbf-4b12b8a86f27/splash.png","bundle_id":"com.apple.app","team_id":"8SQ493N52G"},"android":{"icon_path":"100d9050-7c76-4427-8221-ed3a3a1b4163/icon/f766ec42-3518-44a2-bb0a-c8d3e76c32d9/icon.png","splash_path":"100d9050-7c76-4427-8221-ed3a3a1b4163/splash/52f30302-0df9-47a5-9cbf-4b12b8a86f27/splash.png","bundle_id":"adasdasdsadasasd","service_file_path":"100d9050-7c76-4427-8221-ed3a3a1b4163/androidFirebaseServiceFile/28c75bcf-600d-4448-948b-78bcac5c2bc5/androidFirebaseServiceFile.json","expected_output":"apk&aab"},"both":{"enableOneSignal":true,"enableApptileAnalytics":false,"enableFBSDK":false,"enableSourceMap":false,"enableCleverTap":false,"enableKlaviyo":false,"enableMoEngage":false},"integrations":{"oneSignal":{"onesignal_app_id":"525896ec-34c0-4131-804c-79f8706ab6e4"},"metaAds":{"FacebookAppId":"570147808995354","FacebookClientToken":"5c95639f9583bc38a9fea5075b04b1cf","FacebookDisplayName":"Pilgrim Staging","FacebookAutoLogAppEventsEnabled":"YES","FacebookAdvertiserIDCollectionEnabled":"NO"},"appsflyer":{"devkey":"xxx","appId":"xxx"},"moengage":{"appId":"xxx","datacenter":"xxx"}},"assets":[{"fileName":"icon.png","assetClass":"icon","url":"https://apptile-demo-build-assets.s3.us-east-1.amazonaws.com/100d9050-7c76-4427-8221-ed3a3a1b4163/icon/f766ec42-3518-44a2-bb0a-c8d3e76c32d9/icon.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIA2Q4O3KBMOLZE7QST%2F20250207%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250207T115237Z&X-Amz-Expires=3600&X-Amz-Signature=a389ec27158e62ca0812c204613d5d8f1e04ef3411b4f4b9f9b24cf6fe801eca&X-Amz-SignedHeaders=host&x-id=GetObject"},{"fileName":"splash.png","assetClass":"splash","url":"https://apptile-demo-build-assets.s3.us-east-1.amazonaws.com/100d9050-7c76-4427-8221-ed3a3a1b4163/splash/52f30302-0df9-47a5-9cbf-4b12b8a86f27/splash.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=AKIA2Q4O3KBMOLZE7QST%2F20250207%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20250207T115237Z&X-Amz-Expires=3600&X-Amz-Signature=69f503f05715f1fa0b7d4c03e8b7fd9d33a293aa8ed1d4911833e355b524f2e8&X-Amz-SignedHeaders=host&x-id=GetObject"}],"secrets":[{"id":"185c6774-c5fb-454d-8a02-27083871f602","appId":"100d9050-7c76-4427-8221-ed3a3a1b4163","secretClass":"keyAlias","deletedAt":null,"createdAt":"2025-02-07T11:46:00.980Z","secret":"saddsa"},{"id":"bfbd6660-f7e2-42a0-8a13-fa3940f1b9da","appId":"100d9050-7c76-4427-8221-ed3a3a1b4163","secretClass":"storePassword","deletedAt":null,"createdAt":"2025-02-07T11:46:00.985Z","secret":"adssad"},{"id":"625823e4-54dc-40eb-ad6e-117ca2de84ed","appId":"100d9050-7c76-4427-8221-ed3a3a1b4163","secretClass":"keyPassword","deletedAt":null,"createdAt":"2025-02-07T11:46:00.987Z","secret":"adsdsa"}],"feature_flags":{"ENABLE_ONESIGNAL":true,"ENABLE_SEGMENT_ANALYTICS":false,"ENABLE_FBSDK":true,"ENABLE_CLEVERTAP":false,"ENABLE_KALVIYO":false,"ENABLE_MOENGAGE":true}};
      */
      const SDK_PATH = existingConfig.SDK_PATH;
      console.log("writing apptile.config.json to: ", openedApp.repoPath);
      await writeFile(
        path.resolve(openedApp.repoPath, "apptile.config.json"),
        JSON.stringify(
          {
            "-comment-":
              "This file is generated by cli. You can edit it to use different configs, but will be overwritten on regeneration.",
            ...appconfig,
            SDK_PATH: path.resolve(
              openedApp.repoPath,
              "../ReactNativeTSProjeect"
            ),
            APP_ID: appId,
            APPTILE_BACKEND_URL: existingConfig.APPTILE_BACKEND_URL,
            APPCONFIG_SERVER_URL: existingConfig.APPCONFIG_SERVER_URL,
            APPTILE_BUILD_MANAGER_URL: existingConfig.APPTILE_BUILD_MANAGER_URL,
          },
          null,
          2
        )
      );
    } else {
      console.error(
        "Cannot generate apptileconfig because either app is not open or cookie is not set!"
      );
    }
  } catch (err) {
    console.error("Failed to generate apptileconfig: ", err);
  }
}

export async function cloneApp(
  workspaceId: number,
  manifest: AppManifest,
  appIntegrations: AppIntegration[],
  infoFromApptileIO: InfoFromApptileIO
) {
  try {
    sendLog("cloning app");
    const workspace = getWorkspace(workspaceId);
    if (workspace) {
      const entries = await readdir(path.resolve(workspace.location), {
        withFileTypes: true,
      });
      for (let entry of entries) {
        if (entry.isDirectory() && entry.name !== "ReactNativeTSProjeect") {
          let apptileConfig: any = {};
          try {
            const apptileConfigRaw = await readFile(
              path.resolve(
                workspace.location,
                entry.name,
                "apptile.config.json"
              ),
              { encoding: "utf8" }
            );
            apptileConfig = JSON.parse(apptileConfigRaw);
          } catch (err) {
            console.error(
              "Found a folder with invalid or nonexistent apptile.config.json",
              err
            );
          }
          const appId = apptileConfig.APP_ID;
          if (manifest.uuid == appId) {
            throw new Error(
              `clone already exists for ${appId} at ${path.resolve(
                workspace.location,
                entry.name
              )}`
            );
          }
        }
      }
      const targetName = createTargetName(manifest.name, manifest.uuid);
      const appLocation = path.resolve(workspace.location, targetName);
      if (manifest.gitRepo) {
        await exec(`git clone --depth=1 ${manifest.gitRepo} ${targetName}`, {
          cwd: workspace.location,
        });
      } else {
        // unzip to location
        await exec(
          `git clone --depth=1 git@github.com:clearsight-dev/apptile-seed.git ${targetName}`,
          { cwd: workspace.location }
        );

        const xcodeEnvLocation = path.resolve(
          workspace.location,
          targetName,
          "ios/.xcode.env"
        );
        let xcodeEnv = await readFile(xcodeEnvLocation, { encoding: "utf8" });
        xcodeEnv = xcodeEnv.replace(
          "# APPTILE_CLI_NODE_PATH_ENTRY",
          `export NODE_BINARY=${process.execPath}`
        );
        await writeFile(xcodeEnvLocation, xcodeEnv);
        // Update Info.plist
        /*
      const infoPlistLocation = path.resolve(appLocation, 'ios/apptileSeed/Info.plist');
      const rawInfoPlist = await readFile(infoPlistLocation, {encoding: 'utf8'});
      const infoPlist = plist.parse(rawInfoPlist) as any;
      infoPlist.APPTILE_API_ENDPOINT = APPTILE_BACKEND_URL;
      infoPlist.APPTILE_UPDATE_ENDPOINT = APPCONFIG_SERVER_URL;
      const updatedPlist = plist.build(infoPlist);
      await writeFile(infoPlistLocation, updatedPlist);
      */
      }

      const SDK_PATH = path.resolve(
        workspace.location,
        "ReactNativeTSProjeect"
      );
      const APP_ID = manifest.uuid;
      const APPCONFIG_SERVER_URL = infoFromApptileIO.appconfigServer;
      let APPTILE_BACKEND_URL = infoFromApptileIO.apptileServer;

      const nets = os.networkInterfaces();
      const localIpInterface = Object.values(nets)
        .flatMap((it) => it)
        .filter(
          (it: any) =>
            it.family === "IPv4" &&
            it.address !== "127.0.0.1" &&
            it.netmask !== "255.255.0.0"
        );
      if (localIpInterface.length === 0) {
        console.error("Could not determine local ip address");
      } else {
        const ip: string = (localIpInterface[0] as any).address;
        if (APPTILE_BACKEND_URL.includes("api.apptile.local")) {
          APPTILE_BACKEND_URL = `http://${ip}:3001`;
        }
      }

      console.log("Writing apptile.config.json");
      await writeFile(
        path.resolve(appLocation, "apptile.config.json"),
        JSON.stringify(
          {
            "-comment-":
              "Partial config. Set apptilecookie and hit generate appconfig in cli to get the rest.",
            SDK_PATH: SDK_PATH,
            APP_ID: APP_ID,
            APPTILE_BACKEND_URL: APPTILE_BACKEND_URL,
            APPCONFIG_SERVER_URL: APPCONFIG_SERVER_URL,
          },
          null,
          2
        )
      );

      // if appintegrations exist that have repos then update package.json and codegen relevant plugins/navs
      console.log("Codegen integrations");
      if (appIntegrations.length > 0) {
        await codegenIntegrations(appLocation, appIntegrations);
      }

      const to = path.resolve(workspace.location, targetName);
      /*
    const appDelegatePath = path.resolve(to, 'ios/apptileSeed/AppDelegate.mm');
    let appDelegate = await readFile(
      appDelegatePath,
      {encoding: 'utf8'}
    );
    appDelegate = appDelegate.replace('__METRO_IP__', `http://${ip}:8081`);
    await writeFile(appDelegatePath, appDelegate)
    sendLog("Start installation");
    */
      console.log("Starting installation for Node packages and PODS");
      await npmInstall(to);
      await podInstall(to);
    } else {
      sendLog("Couldn't find the workspace to clone into");
      throw new Error("workspace doesn't exist");
    }
  } catch (error) {
    console.error("Failed to clone app: ", error);
  }
}

export async function cloneAppEc2(
  workspaceId: number,
  manifest: AppManifest,
  appIntegrations: AppIntegration[],
  infoFromApptileIO: InfoFromApptileIO
) {
  try {
    logger.info("cloning app: " + JSON.stringify(manifest, null, 2));
    const workspace = getWorkspace(workspaceId);
    if (workspace) {
      const entries = await readdir(path.resolve(workspace.location), {
        withFileTypes: true,
      });
      for (let entry of entries) {
        if (entry.isDirectory() && entry.name !== "ReactNativeTSProjeect") {
          let apptileConfig: any = {};
          try {
            const apptileConfigRaw = await readFile(
              path.resolve(
                workspace.location,
                entry.name,
                "apptile.config.json"
              ),
              { encoding: "utf8" }
            );
            apptileConfig = JSON.parse(apptileConfigRaw);
          } catch (err) {
            console.error(
              "Found a folder with invalid or nonexistent apptile.config.json",
              err
            );
          }
          const appId = apptileConfig.APP_ID;
          if (manifest.uuid == appId) {
            throw new Error(
              `clone already exists for ${appId} at ${path.resolve(
                workspace.location,
                entry.name
              )}`
            );
          }
        }
      }
      const targetName = createTargetName(manifest.name, manifest.uuid);
      const appLocation = path.resolve(workspace.location, targetName);
      if (manifest.gitRepo) {
        await exec(`git clone --depth=1 ${manifest.gitRepo} ${targetName}`, {
          cwd: workspace.location,
        });
      } else {
        // unzip to location
        await exec(
          `git clone --depth=1 git@github.com:clearsight-dev/apptile-seed.git ${targetName}`,
          { cwd: workspace.location }
        );
      }

      const SDK_PATH = path.resolve(
        workspace.location,
        "ReactNativeTSProjeect"
      );
      const APP_ID = manifest.uuid;
      let APPCONFIG_SERVER_URL = "https://dev-appconfigs.apptile.io";
      let APPTILE_BACKEND_URL = infoFromApptileIO.apptileServer;
      let APPTILE_BUILD_MANAGER_URL;
      if (process.env.NODE_ENV === "production") {
        APPTILE_BACKEND_URL = `${process.env.APPTILE_SERVER_PROTOCOL}://${process.env.APPTILE_SERVER_ENDPOINT}:${process.env.APPTILE_SERVER_PORT}`;
        APPTILE_BUILD_MANAGER_URL = `${process.env.APPTILE_SERVER_PROTOCOL}://${process.env.APPTILE_SERVER_ENDPOINT}:${process.env.APPTILE_BUILD_MANAGER_PORT}`;
        APPCONFIG_SERVER_URL = "https://appconfigs.apptile.io";
      }

      const to = path.resolve(workspace.location, targetName);

      console.log("Writing apptile.config.json");
      await writeFile(
        path.resolve(appLocation, "apptile.config.json"),
        JSON.stringify(
          {
            "-comment-":
              "Partial config. Set apptilecookie and hit generate appconfig in cli to get the rest.",
            SDK_PATH: SDK_PATH,
            APP_ID: APP_ID,
            APPTILE_BACKEND_URL: APPTILE_BACKEND_URL,
            APPCONFIG_SERVER_URL: APPCONFIG_SERVER_URL,
            APPTILE_BUILD_MANAGER_URL: APPTILE_BUILD_MANAGER_URL,
          },
          null,
          2
        )
      );

      // if appintegrations exist that have repos then update package.json and codegen relevant plugins/navs
      // logger.info("Codegen integrations");
      if (appIntegrations.length > 0) {
        await codegenIntegrations(appLocation, appIntegrations);
      }
      logger.info("Starting installation for Node packages");
      await npmInstall(to);
    } else {
      logger.error("Couldn't find the workspace to clone info");
    }
  } catch (error) {
    logger.error("Failed to clone app: ", error);
  }
}

export async function createAppFolder(
  workspaceId: number,
  manifest: AppManifest,
  infoFromApptileIO: InfoFromApptileIO
) {
  sendLog("Creating folder!");
  const workspace = getWorkspace(workspaceId);
  if (workspace) {
    const entries = await readdir(path.resolve(workspace.location), {
      withFileTypes: true,
    });
    for (let entry of entries) {
      if (entry.isDirectory() && entry.name !== "ReactNativeTSProjeect") {
        let apptileConfig: any = {};
        try {
          const apptileConfigRaw = await readFile(
            path.resolve(workspace.location, entry.name, "apptile.config.json"),
            { encoding: "utf8" }
          );
          apptileConfig = JSON.parse(apptileConfigRaw);
        } catch (err) {
          console.error(
            "Found a folder with invalid or nonexistent apptile.config.json",
            err
          );
        }
        const appId = apptileConfig.APP_ID;
        if (manifest.uuid == appId) {
          throw new Error(
            `clone already exists for ${appId} at ${path.resolve(
              workspace.location,
              entry.name
            )}`
          );
        }
      }
    }
    const targetName = createTargetName(manifest.name, manifest.uuid);
    const appLocation = path.resolve(workspace.location, targetName);

    if (manifest.gitRepo) {
      await exec(`git clone --depth=1 ${manifest.gitRepo} ${targetName}`, {
        cwd: workspace.location,
      });
    } else {
      // unzip to location
      await exec(
        `git clone --depth=1 git@github.com:clearsight-dev/apptile-seed.git ${targetName}`,
        { cwd: workspace.location }
      );
    }
    console.log("Folder created: ", targetName);
    const SDK_PATH = path.resolve(workspace.location, "ReactNativeTSProjeect");
    const APP_ID = manifest.uuid;
    let APPCONFIG_SERVER_URL = "https://dev-appconfigs.apptile.io";
    let APPTILE_BACKEND_URL = infoFromApptileIO.apptileServer;
    let APPTILE_BUILD_MANAGER_URL;
    if (process.env.NODE_ENV === "production") {
      APPTILE_BACKEND_URL = `${process.env.APPTILE_SERVER_PROTOCOL}://${process.env.APPTILE_SERVER_ENDPOINT}:${process.env.APPTILE_SERVER_PORT}`;
      APPTILE_BUILD_MANAGER_URL = `${process.env.APPTILE_SERVER_PROTOCOL}://${process.env.APPTILE_SERVER_ENDPOINT}:${process.env.APPTILE_BUILD_MANAGER_PORT}`;
      APPCONFIG_SERVER_URL = "https://appconfigs.apptile.io";
    }
    await writeFile(
      path.resolve(appLocation, "apptile.config.json"),
      JSON.stringify(
        {
          "-comment-":
            "Partial config. Set apptilecookie and hit generate appconfig in cli to get the rest.",
          SDK_PATH: SDK_PATH,
          APP_ID: APP_ID,
          APPTILE_BACKEND_URL: APPTILE_BACKEND_URL,
          APPCONFIG_SERVER_URL: APPCONFIG_SERVER_URL,
          APPTILE_BUILD_MANAGER_URL: APPTILE_BUILD_MANAGER_URL,
        },
        null,
        2
      )
    );
    console.log("Apptile config file written");
  }
}

export async function listWorkspaces() {
  try {
    const workspaces = getWorkspaces();
    if (workspaces) {
      let result: FileSystem.WorkspaceData[] = [];
      for (let workspace of workspaces) {
        const sdkLocation = path.resolve(
          workspace.location,
          "ReactNativeTSProjeect"
        );

        let { stdout: branchName } = await exec(
          `git rev-parse --abbrev-ref HEAD`,
          { cwd: sdkLocation }
        );
        branchName = branchName.trim();

        // let {stdout: sdkHash} = await exec(`git log -1 --format="%H"`, {cwd: sdkLocation})
        // sdkHash = sdkHash.trim();
        // let { stdout: sdkHash } = await exec(
        //   `git rev-list --left-right --count HEAD...origin/${branchName}`,
        //   { cwd: sdkLocation }
        // );
        // sdkHash = sdkHash.trim();
        const sdkHash = "none";

        let workspaceResult: FileSystem.WorkspaceData = {
          id: workspace.id,
          sdkHash: `${branchName} (${sdkHash})`,
          location: workspace.location,
          repos: [],
        };

        const entries = await readdir(path.resolve(workspace.location), {
          withFileTypes: true,
        });
        for (let entry of entries) {
          const isDir = entry.isDirectory();
          if (isDir && entry.name !== "ReactNativeTSProjeect") {
            const repo: FileSystem.Repo = {
              name: entry.name,
              fullPath: path.resolve(workspace.location, entry.name),
              gitRepo: "",
              appId: "",
              apptileServer: "",
              appconfigServer: "",
              isOpen: false,
              codePushBundles: {
                ios: [],
                android: [],
              },
            };
            // get appid and git branch
            const apptileConfigPath = path.resolve(
              workspace.location,
              entry.name,
              "apptile.config.json"
            );
            try {
              const rawConfig = await readFile(apptileConfigPath, {
                encoding: "utf-8",
              });
              const config = JSON.parse(rawConfig);
              repo.appId = config.APP_ID;
              repo.apptileServer = config.APPTILE_BACKEND_URL;
              repo.appconfigServer = config.APPCONFIG_SERVER_URL;
              repo.isOpen = !!await getOpenApp(config.APP_ID);
            } catch (err) {
              console.error("Failed to get appid from repo: ", err);
              repo.appId = "error";
            }

            try {
              const { stdout, stderr } = await exec(
                `git remote get-url origin`,
                { cwd: path.resolve(workspace.location, entry.name) }
              );
              if (stdout) {
                repo.gitRepo = stdout.trim();
              } else {
                repo.gitRepo = stderr.trim();
              }
            } catch (err) {
              console.error("Failed to get git repository origin ", err);
            }

            // get ios and android folder contents for codePushBundles
            try {
              const bundles = await readdir(
                path.resolve(
                  workspace.location,
                  entry.name,
                  "remoteCode/generated/bundles/ios"
                ),
                { withFileTypes: true }
              );
              for (let bundle of bundles) {
                if (bundle.isDirectory()) {
                  repo.codePushBundles.ios.push({
                    fullPath: path.resolve(bundle.parentPath, bundle.name),
                    timestamp: parseInt(bundle.name),
                  });
                }
              }
            } catch (err) {
              // console.error("Failed to get ios codepush bundles", err);
            }

            try {
              const bundles = await readdir(
                path.resolve(
                  workspace.location,
                  entry.name,
                  "remoteCode/generated/bundles/android"
                ),
                { withFileTypes: true }
              );
              for (let bundle of bundles) {
                if (bundle.isDirectory()) {
                  repo.codePushBundles.android.push({
                    fullPath: path.resolve(bundle.parentPath, bundle.name),
                    timestamp: parseInt(bundle.name),
                  });
                }
              }
            } catch (err) {
              // console.error("Failed to get android codepush bundles", err);
            }

            workspaceResult.repos.push(repo);
          }
        }
        result.push(workspaceResult);
      }

      return result;
    } else {
      throw new Error("Failed when getting workspaces");
    }
  } catch (err) {
    console.error("Failed to get workspaces: ", err);
    throw err;
  }
}

export async function getPluginBundle(appId: string) {
  // Get opened app. If its not open return error
  const openedApp = await getOpenApp(appId);
  if (!openedApp) {
    throw new Error("Open the app first.")
  } else {
    // Get location from opened app
    const appLocation = openedApp.repoPath;
    // list all plugins from the folder
    // get hash for plugins
    const remoteCode = path.resolve(appLocation, 'remoteCode')

    const pluginsRoot = path.resolve(remoteCode, 'plugins');
    const plugins: string[] = [];
    try {
      const pluginEntries = await readdir(pluginsRoot, {withFileTypes: true});
      for (let entry of pluginEntries) {
        if (entry.isDirectory()) {
          plugins.push(entry.name);
        }
      }
    } catch (err: any) {
      if (err?.code !== 'ENOENT') {
        console.error("Unhandelable error", err);
        throw err;
      }
    }

    const generatedRoot = path.resolve(remoteCode, 'generated');
    const generatedFolderName = sha256(plugins.join('_')).toString();
    let bundlePath = path.resolve(generatedRoot, generatedFolderName, 'dist/bundle.js');

    try {
      await stat(bundlePath);
    } catch (err: any) {
      if (err?.code === 'ENOENT') {
        const compileResult = await compileMultiplePlugins(remoteCode, plugins, generatedFolderName);
        if (!compileResult.errors) {
          bundlePath = path.resolve(generatedRoot, 'dist/bundle.js');
        } else {
          console.error('COMPILATION FAILED: ', compileResult)
          throw new Error(compileResult.message);
        }
      } else {
        throw err;
      }
    }

    return bundlePath;
  }
}

export async function compilePluginBundle(appId: string) {
  const openedApp = await getOpenApp(appId);
  if (!openedApp) {
    throw new Error("Open the app first.");
  } else {
    const appLocation = openedApp.repoPath;
    const remoteCode = path.resolve(appLocation, 'remoteCode');
    const pluginsRoot = path.resolve(remoteCode, 'plugins');
    const pluginEntries = await readdir(pluginsRoot, {withFileTypes: true});
    const plugins: string[] = [];
    for (let entry of pluginEntries) {
      if (entry.isDirectory()) {
        plugins.push(entry.name);
      }
    }

    const generatedFolderName = sha256(plugins.join('_')).toString();
    const result = await compileMultiplePlugins(remoteCode, plugins, generatedFolderName);
    return result;
  }
}

export async function getLocalBundles(appLocation: string) {
  const iosBundlesFolder = path.resolve(appLocation, 'remoteCode/generated/bundles/ios');
  let iosBundles: Array<{fullPath: string; timestamp: number;}> = [];
  const androidBundlesFolder = path.resolve(appLocation, 'remoteCode/generated/bundles/android');
  let androidBundles: Array<{fullPath: string; timestamp: number;}> = [];
  try {
    const entries = await readdir(
      iosBundlesFolder,
      {withFileTypes: true}
    );

    for (let entry of entries) {
      iosBundles.push({
        fullPath: path.resolve(iosBundlesFolder, entry.name, 'bundle.zip'),
        timestamp: parseInt(entry.name)
      });
    }
  } catch (err) {
    console.error("Failed to get local ios bundles: ", err);
  }

  try {
    const entries = await readdir(
      androidBundlesFolder,
      {withFileTypes: true}
    );

    for (let entry of entries) {
      androidBundles.push({
        fullPath: path.resolve(androidBundlesFolder, entry.name, 'bundle.zip'),
        timestamp: parseInt(entry.name)
      });
    }
  } catch (err) {
    console.error("Failed to get local ios bundles: ", err);
  }

  return {
    ios: iosBundles, 
    android: androidBundles
  };
}

export async function getAppLocation(workspaceId: number, appId: string) {
  const workspace = getWorkspace(workspaceId);
  if (workspace) {
    const entries = await readdir(workspace.location, {withFileTypes: true});
    let appLocation = '';
    for (let entry of entries) {
      if (!['ReactNativeTSProjeect', '.DS_Store'].includes(entry.name)) {
        try {
          const apptileConfigPath = path.resolve(workspace.location, entry.name, 'apptile.config.json');
          const rawConfig = await readFile(apptileConfigPath, {encoding: 'utf8'});
          const config = JSON.parse(rawConfig);
          if (appId === config.APP_ID) {
            appLocation = path.resolve(workspace.location, entry.name);
            break;
          }
        } catch (err) {
          console.error("Failed to read apptile config", err);
        }
      }
    }
    return appLocation;
  } else {
    console.error("Could not find workspace");
    throw new Error("Not found")
  }
}

export async function ensureDefaultWorkspace() {
  console.log("ensuring default workspace")
  // ensure that workspaces table has an entry linking path to ~/apptile-cli-home
  const defaultWorkspaceLocation = path.resolve(homeDir, 'apptile-cli-home');
  const workspace = getWorkspaceByLocation(defaultWorkspaceLocation);
  if (!workspace) {
    console.log("Creating database entry")
    createWorkspace(defaultWorkspaceLocation);
  }

  let create = false;
  try {
    await readdir(defaultWorkspaceLocation);
  } catch (err: any) {
    if (err?.code === 'ENOENT') {
      console.log("default cli home folder doesn't exist")
      create = true;
    } else {
      console.error("Failed to verify existence of default workspace", err);
      throw err;
    }
  }

  let clone = false;
  const defaultSDKLocation = path.resolve(defaultWorkspaceLocation, 'ReactNativeTSProjeect');
  if (!create) {
    try {
      await stat(defaultSDKLocation);
    } catch(err: any) {
      if (err?.code === 'ENOENT') {
        console.log("Default SDK clone doesn't exist")
        clone = true;
      } else {
        console.error("Failed to verify existence of sdk");
        throw err;
      }
    }
  }

  const expectedRemote = 'git@github.com:clearsight-dev/ReactNativeTSProjeect.git';
  let remoteLocation: string = '';
  if (create) {
    console.log("Creating ~/apptile-cli-home")
    await mkdir(defaultWorkspaceLocation);
    console.log("Cloning ReactNativeTSProjeect SDK")
    const {stdout, stderr} = await exec(
      `git clone --depth=1 --branch=scion/v3-roadmap ${expectedRemote}`, 
      {cwd: defaultWorkspaceLocation}
    );

      remoteLocation = expectedRemote;
      console.log(stdout);
      console.log(stderr);
  } else if (clone) {
    console.log("Cloning SDK into existing ~/apptile-cli-home folder")
    const {stdout, stderr} = await exec(
      `git clone --depth=1 --branch=scion/v3-roadmap ${expectedRemote}`, 
      {cwd: defaultWorkspaceLocation}
    );

    remoteLocation = expectedRemote;
    console.log(stdout);
    console.log(stderr);
  } else {
    console.log("Checking validity of sdk in default workspace at: ", defaultSDKLocation);
    const {stdout, stderr}  = await exec(
      `git remote get-url origin`,
      {cwd: defaultSDKLocation}
    );

    remoteLocation = stdout.trim();
  }

  if (remoteLocation !== expectedRemote) {
    throw new Error(`workspace is corrupted. Expceted sdk to be cloned from ${expectedRemote} but found ${remoteLocation}`);
  }

  return `Paste the following in your browser console to get started: 
localStorage.setItem('plugin-server-url', 'http://localhost:3100/plugin-server'); localStorage.setItem('enablevim', 'yes'); window.location.reload();
`;
}

type ProjectConfig = {
  SDK_PATH: string;
  APP_ID: string;
  APPTILE_BACKEND_URL: string;
  APPCONFIG_SERVER_URL: string;
  APPTILE_BUILD_MANAGER_URL: string;
};

function narrowProjectConfig(data: any): data is ProjectConfig {
  return (
    (typeof(data?.SDK_PATH) === "string") && 
    (typeof(data?.APP_ID) === "string") && 
    (typeof(data?.APPTILE_BACKEND_URL) === "string") && 
    (typeof(data?.APPCONFIG_SERVER_URL) === "string") 
  );
}

export async function getAppConfigsFromFS(appId: string): Promise<ProjectConfig> {
  const openedApp = await getOpenApp(appId);
  if (openedApp) {
    const configPath = path.resolve(openedApp.repoPath, 'apptile.config.json');
    const rawConfig = await readFile(configPath, {encoding: 'utf8'});
    const config = JSON.parse(rawConfig);
    if (narrowProjectConfig(config)) {
      return config;
    } else {
      throw new Error("Could not validate the apptile.config.json file's type");
    }
  } else {
    throw new Error("Could not find opened app");
  }
}

export async function getWebSDKBundleFromFS(): Promise<any> {

  const webSDKBundle = createReadStream(path.resolve(__dirname, '../dist/web-sdk-bundle.js'), 'utf-8');

  return webSDKBundle;  
  
}

export interface FileNode {
  name: string;
  type: 'file' | 'folder';
  path: string;
  children?: FileNode[];
}

// Helper function to recursively build the file tree with paths relative to a basePath
async function _getFileTreeRecursive(currentDirPath: string, basePath: string): Promise<FileNode[]> {
  const entries = await fs.readdir(currentDirPath, { withFileTypes: true });
  const nodes: FileNode[] = [];

  for (const entry of entries) {
    const fullEntryPath = path.join(currentDirPath, entry.name);
    // Calculate path relative to the initial basePath
    const relativePath = path.relative(basePath, fullEntryPath);

    const node: FileNode = {
      name: entry.name,
      type: entry.isDirectory() ? 'folder' : 'file',
      path: relativePath, // Store relative path
    };

    if (entry.isDirectory()) {
      // Recursively call with the full path of the directory and the original basePath
      node.children = await _getFileTreeRecursive(fullEntryPath, basePath);
    }
    nodes.push(node);
  }
  return nodes;
}

export async function getFileTree(dirPath: string): Promise<FileNode[]> {
  return _getFileTreeRecursive(dirPath, dirPath);
}

interface ManageSourceItemParams {
  itemType: 'plugin' | 'navigator';
  itemName: string;
  relativePath: string;
  isFolder: boolean;
  action: 'create' | 'delete';
}

export async function manageSourceFileOrFolder(params: ManageSourceItemParams, sourceBasePath: string) {
  const { itemType, itemName, relativePath, isFolder, action } = params;

  // Validate relativePath: must not be empty, '.', absolute, or contain '..'
  if (!relativePath || relativePath.trim() === '' || relativePath === '.') {
    logger.warn(`manageSourceFileOrFolder: Invalid relativePath - empty or '.'`);
    throw new Error('Invalid relativePath: Must specify a file or folder name within the source directory.');
  }
  if (path.isAbsolute(relativePath)) {
    logger.warn(`manageSourceFileOrFolder: Invalid relativePath - absolute path provided: ${relativePath}`);
    throw new Error('Invalid relativePath: Must not be an absolute path.');
  }

  const normalizedRelativePath = path.normalize(relativePath);
  // Disallow any '..' segments in the path for security.
  if (normalizedRelativePath.split(path.sep).includes('..') || normalizedRelativePath.startsWith('..'+path.sep) || normalizedRelativePath === '..') {
    logger.warn(`manageSourceFileOrFolder: Invalid relativePath - contains '..': ${relativePath} (normalized: ${normalizedRelativePath})`);
    throw new Error('Invalid relativePath: ".." segments are not allowed, path traversal attempt suspected.');
  }

  const targetFullPath = path.join(sourceBasePath, normalizedRelativePath);

  // Security check: ensure the resolved targetPath is strictly within sourceBasePath
  if (!targetFullPath.startsWith(sourceBasePath + path.sep)) {
    logger.error(`manageSourceFileOrFolder: Path integrity check failed. Resolved path: ${targetFullPath}. Base source path: ${sourceBasePath}`);
    throw new Error('Invalid path: Operation must be strictly within the designated source sub-directory.');
  }

  try {
    if (action === 'create') {
      if (isFolder) {
        await mkdir(targetFullPath, { recursive: true });
        const message = `Folder '${relativePath}' created successfully in ${itemType} '${itemName}'.`;
        sendLog(message);
        logger.info(message + ` Path: ${targetFullPath}`);
        return { success: true, message };
      } else {
        const parentDir = path.dirname(targetFullPath);
        await mkdir(parentDir, { recursive: true });
        await writeFile(targetFullPath, '', { encoding: 'utf8' }); // Create an empty file
        const message = `File '${relativePath}' created successfully in ${itemType} '${itemName}'.`;
        sendLog(message);
        logger.info(message + ` Path: ${targetFullPath}`);
        return { success: true, message };
      }
    } else if (action === 'delete') {
      let statsInfo;
      try {
        statsInfo = await stat(targetFullPath);
      } catch (e: any) {
        if (e.code === 'ENOENT') {
          const message = `Cannot delete '${relativePath}' in ${itemType} '${itemName}': Path does not exist.`;
          logger.warn(message + ` Path: ${targetFullPath}`);
          throw new Error(message);
        }
        logger.error(`manageSourceFileOrFolder: Error stating path ${targetFullPath} before deletion: ${e.message}`);
        throw e; // re-throw other stat errors
      }

      if (isFolder) {
        if (!statsInfo.isDirectory()) {
          const message = `Cannot delete '${relativePath}' in ${itemType} '${itemName}': Not a directory.`;
          logger.warn(message + ` Path: ${targetFullPath}`);
          throw new Error(message);
        }
        await rm(targetFullPath, { recursive: true, force: true });
        const message = `Folder '${relativePath}' deleted successfully from ${itemType} '${itemName}'.`;
        sendLog(message);
        logger.info(message + ` Path: ${targetFullPath}`);
        return { success: true, message };
      } else {
        if (!statsInfo.isFile()) {
          const message = `Cannot delete '${relativePath}' in ${itemType} '${itemName}': Not a file.`;
          logger.warn(message + ` Path: ${targetFullPath}`);
          throw new Error(message);
        }
        await unlink(targetFullPath);
        const message = `File '${relativePath}' deleted successfully from ${itemType} '${itemName}'.`;
        sendLog(message);
        logger.info(message + ` Path: ${targetFullPath}`);
        return { success: true, message };
      }
    } else {
      const message = `Invalid action: ${action}. Must be 'create' or 'delete'.`;
      logger.warn(`manageSourceFileOrFolder: ${message}`);
      throw new Error(message);
    }
  } catch (error: any) {
    const errorMessage = `Operation failed for ${itemType} '${itemName}', path '${relativePath}', action '${action}'. Error: ${error.message}`;
    logger.error(`manageSourceFileOrFolder: ${errorMessage}`, error);
    throw error;
  }
}