/*
 * Copyright (c) 2025 Huawei Device Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
import * as fs from "fs";
import * as path from "path";
import * as os from "os";
import * as vscode from "vscode";
import {getDevecoCangjiePath, getOhosVersion} from "./utils";

export async function resolveAndWriteEnvVars(cjmpSdkHome: string): Promise<void> {
  const workspaceRoot = vscode.workspace.workspaceFolders?.[0]?.uri.fsPath;
  if (!workspaceRoot) {
    vscode.window.showErrorMessage("No workspace folder open.");
    return;
  }
  const isWindows = os.platform() === "win32";
  const platformKey = isWindows ? "windows" : "osx";
  const buildEnvVars = isWindows
    ? parseBatEnvVars(workspaceRoot, cjmpSdkHome)
    : parseShEnvVars(workspaceRoot, cjmpSdkHome);
  const cangjieOhosEnvVars = buildCangjieOhosEnv();
  const newEnv: Record<string, string> = { ...buildEnvVars, ...cangjieOhosEnvVars };
  const config = vscode.workspace.getConfiguration();
  const current = config.get<Record<string, string | null>>(`terminal.integrated.env.${platformKey}`) ?? {};
  const merged: Record<string, string | null> = { ...current, ...newEnv };
  if (isSameEnv(current, merged)) {
    return;
  }
  try {
    await config.update(`terminal.integrated.env.${platformKey}`, merged, vscode.ConfigurationTarget.Workspace);
    vscode.window.showInformationMessage(`Successfully wrote ${Object.keys(newEnv).length} environment variables to settings.json`);
  } catch (err) {
    vscode.window.showWarningMessage(`Failed to write env to settings.json: ${err instanceof Error ? err.message : String(err)}`);
  }
}

function buildCangjieOhosEnv(): Record<string, string> {
  let sdkPath = getDevecoCangjiePath();
  if (sdkPath === null || sdkPath === undefined) {
    return {};
  }
  const version = getOhosVersion(sdkPath);
  if (!version) {
    vscode.window.showErrorMessage("Failed to read version from oh-uni-package.json.");
    return {};
  }

  const parts = version.split(".");
  const branch = `${parts[0]}.${parts[1]}`; // "5.1" / "6.0" / "6.1" ...
  const isWindows = os.platform() === "win32";

  let libs: Record<string, string>;
  if (branch === "5.1") {
    const llvmBase = path.join(sdkPath, "build", "linux_ohos_aarch64_llvm");
    libs = {
      AARCH64_LIBS: path.join(llvmBase, "ohos"),
      AARCH64_KIT_LIBS: path.join(llvmBase, "kit"),
      AARCH64_MACRO_LIBS: isWindows
        ? path.join(sdkPath, "build", "x86_64-w64-mingw32", "macro", "ohos")
        : path.join(llvmBase, "ng_macro", "ohos"),
    };
  } else {
    const cjnativeBase = path.join(sdkPath, "api", "lib", "linux_ohos_aarch64_cjnative");
    libs = {
      AARCH64_LIBS: path.join(cjnativeBase, "ohos"),
      AARCH64_KIT_LIBS: path.join(cjnativeBase, "kit"),
      AARCH64_MACRO_LIBS: path.join(sdkPath, "api", "macro", "ohos"),
    };
  }

  return {
    DEVECO_CANGJIE_HOME: sdkPath,
    ABI: "arm64-v8a",
    ...libs,
  };
}

function isSameEnv(existingEnv: Record<string, string | null>, mergedEnv: Record<string, string | null>): boolean {
  const existingKeys = Object.keys(existingEnv);
  const mergedKeys = Object.keys(mergedEnv);
  if (existingKeys.length !== mergedKeys.length) {
    return false;
  }
  for (const key of existingKeys) {
    if (!Object.prototype.hasOwnProperty.call(mergedEnv, key) || existingEnv[key] !== mergedEnv[key]) {
      return false;
    }
  }
  return true;
}

function parseBatEnvVars(workspaceRoot: string, cjmpSdkHome: string): Record<string, string> {
  const batPath = path.join(workspaceRoot, "build.bat");
  if (!fs.existsSync(batPath)) {
    throw new Error(`build.bat not found at ${batPath}`);
  }
  const content = fs.readFileSync(batPath, "utf-8");
  const lines = content.split(/\r?\n/);
  const vars: Record<string, string> = {
    CJMP_SDK_HOME: cjmpSdkHome,
    SCRIPT_DIR: workspaceRoot,
    ANDROID_SDK_ROOT: process.env.ANDROID_SDK_ROOT || "",
    CD: workspaceRoot,
  };
  const setPattern = /^\s*set\s+"?([A-Za-z_][A-Za-z0-9_]*)=([^"]*)"?\s*$/i;
  for (const rawLine of lines) {
    const line = rawLine.trim();
    if (line.startsWith("@REM") || line.startsWith("REM") || line === "") {
      continue;
    }
    const m = line.match(setPattern);
    if (!m) continue;
    const key = m[1];
    let value = m[2];
    if (/^%\d/.test(value) || /^%%/.test(value)) continue;
    if (key === "file_count" || key === "copied_count") continue;
    value = resolveVarsBat(value, vars);
    value = normalizePathSep(value, true);
    vars[key] = value;
  }
  const exclude = new Set([
    "CD",
    "file_count",
    "copied_count",
    "ext",
    "dep_path",
    "dep_file",
    "pkg_name",
    "so_file",
    "cjo_file",
    "cache_file",
    "copy_cjo",
    "PYTHON_SCRIPT",
  ]);
  for (const k of exclude) delete vars[k];
  return vars;
}

function parseShEnvVars(workspaceRoot: string, cjmpSdkHome: string): Record<string, string> {
  const shPath = path.join(workspaceRoot, "build.sh");
  if (!fs.existsSync(shPath)) {
    throw new Error(`build.sh not found at ${shPath}`);
  }
  const content = fs.readFileSync(shPath, "utf-8");
  const lines = content.split(/\r?\n/);
  const vars: Record<string, string> = {
    CJMP_SDK_HOME: cjmpSdkHome,
    SCRIPT_DIR: workspaceRoot,
    ANDROID_SDK_ROOT: process.env.ANDROID_SDK_ROOT || "",
  };
  const assignPattern = /^\s*(?:export\s+)?([A-Za-z_][A-Za-z0-9_]*)=["']?([^"']*)["']?\s*$/;
  const exportedKeys = new Set<string>();
  for (const rawLine of lines) {
    const line = rawLine.trim();
    if (line.startsWith("#") || line === "") continue;
    const m = line.match(assignPattern);
    if (!m) continue;
    const key = m[1];
    let value = m[2];
    if (/\$\(/.test(value)) continue;
    if (/^\$[0-9]/.test(value)) continue;
    const isExport = /^\s*export\s+/.test(line);
    if (isExport) {
      exportedKeys.add(key);
    }
    value = resolveVarsSh(value, vars);
    value = normalizePathSep(value, false);
    vars[key] = value;
  }
  const output: Record<string, string> = {};
  exportedKeys.add("CJMP_SDK_HOME");
  for (const k of exportedKeys) {
    if (vars[k] !== undefined) {
      output[k] = vars[k];
    }
  }
  const exclude = new Set(["SCRIPT_DIR"]);
  for (const k of exclude) delete output[k];
  return output;
}

function resolveVarsBat(value: string, vars: Record<string, string>): string {
  let result = value;
  let prevResult = "";
  let maxIter = 10;
  while (result !== prevResult && maxIter-- > 0) {
    prevResult = result;
    result = result.replace(/!([A-Za-z_][A-Za-z0-9_]*)!/g, (_, k) =>
      vars[k] !== undefined ? vars[k] : `!${k}!`
    );
    result = result.replace(/%([A-Za-z_][A-Za-z0-9_]*)%/g, (_, k) =>
      vars[k] !== undefined ? vars[k] : `%${k}%`
    );
  }
  return result;
}

function resolveVarsSh(value: string, vars: Record<string, string>): string {
  let result = value;
  let prevResult = "";
  let maxIter = 10;
  while (result !== prevResult && maxIter-- > 0) {
    prevResult = result;
    result = result.replace(/\$\{([A-Za-z_][A-Za-z0-9_]*)\}/g, (_, k) =>
      vars[k] !== undefined ? vars[k] : `\${${k}}`
    );
    result = result.replace(
      /\$([A-Za-z_][A-Za-z0-9_]*)/g,
      (full, k) => (vars[k] !== undefined ? vars[k] : full)
    );
  }
  return result;
}

function normalizePathSep(p: string, isWindows: boolean): string {
  if (isWindows) {
    return p.replace(/\//g, "\\");
  }
  return p.replace(/\\/g, "/");
}