/*
 * dart-sass.ts
 *
 * Copyright (C) 2020-2026 Posit Software, PBC
 */
import { join } from "../deno_ral/path.ts";

import { architectureToolsPath } from "./resources.ts";
import { execProcess } from "./process.ts";
import { TempContext } from "./temp.ts";
import { lines } from "./text.ts";
import { debug, info } from "../deno_ral/log.ts";
import { existsSync } from "../deno_ral/fs.ts";
import { warnOnce } from "./log.ts";
import { isWindows } from "../deno_ral/platform.ts";

export function dartSassInstallDir() {
  return architectureToolsPath("dart-sass");
}

export async function dartSassVersion() {
  return await dartCommand(["--version"]);
}

export async function dartCompile(
  input: string,
  outputFilePath: string,
  temp: TempContext,
  loadPaths?: string[],
  compressed?: boolean,
): Promise<string | undefined> {
  // Write the scss to a file
  // We were previously passing it via stdin, but that can be overflowed
  const inputFilePath = temp.createFile({ suffix: ".scss" });

  // Write the css itself to a file
  Deno.writeTextFileSync(inputFilePath, input);
  const args = [
    inputFilePath,
    outputFilePath,
    "--style",
    compressed ? "compressed" : "expanded",
    "--quiet", // Remove this flag to get depedency warnings from SASS
  ];

  if (loadPaths) {
    loadPaths.forEach((loadPath) => {
      args.push(`--load-path=${loadPath}`);
    });
  }

  await dartCommand(args);
  return outputFilePath;
}

/**
 * Options for dartCommand
 */
export interface DartCommandOptions {
  /**
   * Override the dart-sass install directory.
   * Used for testing with non-standard paths (spaces, accented characters).
   */
  installDir?: string;
}

/**
 * Resolve the dart-sass command and its base arguments.
 *
 * On Windows, calls dart.exe + sass.snapshot directly instead of going
 * through sass.bat. The bundled sass.bat is a thin wrapper generated by
 * dart_cli_pkg that just runs:
 *   "%SCRIPTPATH%\src\dart.exe" "%SCRIPTPATH%\src\sass.snapshot" %arguments%
 *
 * Template source:
 *   https://github.com/google/dart_cli_pkg/blob/main/lib/src/templates/standalone/executable.bat.mustache
 * Upstream issue to ship standalone .exe instead of .bat + dart.exe:
 *   https://github.com/google/dart_cli_pkg/issues/67
 *
 * Bypassing sass.bat avoids multiple .bat file issues on Windows:
 * - Deno quoting bugs with spaced paths (#13997)
 * - cmd.exe OEM code page misreading UTF-8 accented paths (#14267)
 * - Enterprise group policy blocking .bat execution (#6651)
 */
function resolveSassCommand(options?: DartCommandOptions): {
  cmd: string;
  baseArgs: string[];
} {
  const installDir = options?.installDir;
  if (installDir == null) {
    // Only check env var override when no explicit installDir is provided.
    // If QUARTO_DART_SASS doesn't exist on disk, fall through to use the
    // bundled dart-sass at the default architectureToolsPath.
    const dartOverrideCmd = Deno.env.get("QUARTO_DART_SASS");
    if (dartOverrideCmd) {
      if (!existsSync(dartOverrideCmd)) {
        warnOnce(
          `Specified QUARTO_DART_SASS does not exist, using built in dart sass.`,
        );
      } else {
        return { cmd: dartOverrideCmd, baseArgs: [] };
      }
    }
  }

  const sassDir = installDir ?? architectureToolsPath("dart-sass");

  if (isWindows) {
    return {
      cmd: join(sassDir, "src", "dart.exe"),
      baseArgs: [join(sassDir, "src", "sass.snapshot")],
    };
  }

  return { cmd: join(sassDir, "sass"), baseArgs: [] };
}

export async function dartCommand(
  args: string[],
  options?: DartCommandOptions,
) {
  const { cmd, baseArgs } = resolveSassCommand(options);

  const result = await execProcess({
    cmd,
    args: [...baseArgs, ...args],
    stdout: "piped",
    stderr: "piped",
  });

  if (result.success) {
    if (result.stderr) {
      info(result.stderr);
    }
    return result.stdout;
  } else {
    debug(`[DART cmd]    : ${cmd}`);
    debug(`[DART args]   : ${[...baseArgs, ...args].join(" ")}`);
    debug(`[DART stdout] : ${result.stdout}`);
    debug(`[DART stderr] : ${result.stderr}`);

    const errLines = lines(result.stderr || "");
    // truncate the last 2 lines (they include a pointer to the temp file containing
    // all of the concatenated sass, which is more or less incomprehensible for users.
    const errMsg = errLines.slice(0, errLines.length - 2).join("\n");
    throw new Error("Theme file compilation failed:\n\n" + errMsg);
  }
}
