/// <reference types="node" />

import { spawn } from "child_process";
import path from "path";
// import { fileURLToPath } from 'url'; // No longer needed here
import { Logger } from "pino";
import fsPromises from "fs/promises";
import { existsSync } from "fs";
import { SwiftCliResponse } from "../types/index.js";

let resolvedCliPath: string | null = null;
const INVALID_PATH_SENTINEL = "PEEKABOO_CLI_PATH_RESOLUTION_FAILED";

function determineSwiftCliPath(packageRootDirForFallback?: string): string {
  const envPath = process.env.PEEKABOO_CLI_PATH;
  if (envPath) {
    try {
      if (existsSync(envPath)) {
        return envPath;
      }
      // If envPath is set but invalid, fall through to use packageRootDirForFallback
    } catch (_err) {
      /* Fall through if existsSync fails */
    }
  }

  if (packageRootDirForFallback) {
    return path.resolve(packageRootDirForFallback, "peekaboo");
  }

  // If neither PEEKABOO_CLI_PATH is valid nor packageRootDirForFallback is provided,
  // this is a critical failure in path determination.
  return INVALID_PATH_SENTINEL;
}

export function initializeSwiftCliPath(packageRootDir: string): void {
  if (!packageRootDir) {
    // If PEEKABOO_CLI_PATH is also not set or invalid, this will lead to INVALID_PATH_SENTINEL
    // Allow determineSwiftCliPath to handle this, and the error will be caught by getInitializedSwiftCliPath
  }
  resolvedCliPath = determineSwiftCliPath(packageRootDir);
  // No direct logging here; issues will be caught by getInitializedSwiftCliPath
}

function getInitializedSwiftCliPath(logger: Logger): string {
  // Logger is now mandatory
  if (!resolvedCliPath || resolvedCliPath === INVALID_PATH_SENTINEL) {
    const errorMessage = "Peekaboo Swift CLI path is not properly initialized or resolution failed. " +
      `Resolved path: '${resolvedCliPath}'. Ensure PEEKABOO_CLI_PATH is valid or ` +
      "initializeSwiftCliPath() was called with a correct package root directory at startup.";
    logger.error(errorMessage);
    // Throw an error to prevent attempting to use an invalid path
    throw new Error(errorMessage);
  }
  return resolvedCliPath;
}

function mapExitCodeToErrorMessage(
  exitCode: number,
  stderr: string,
): { message: string, code: string } {
  const defaultMessage = stderr.trim()
    ? `Peekaboo CLI Error: ${stderr.trim()}`
    : `Swift CLI execution failed (exit code: ${exitCode})`;
  const errorCodeMap: { [key: number]: { message: string, code: string } } = {
    1: { message: "An unknown error occurred in the Swift CLI.", code: "SWIFT_CLI_UNKNOWN_ERROR" },
    7: { message: "The specified application is running but has no capturable windows. Try setting 'capture_focus' to 'foreground' to un-hide application windows.", code: "SWIFT_CLI_NO_WINDOWS_FOUND" },
    10: { message: "No displays available for capture.", code: "SWIFT_CLI_NO_DISPLAYS" },
    11: {
      message: "Screen Recording permission is not granted. Please enable it in System Settings > Privacy & Security > Screen Recording.",
      code: "SWIFT_CLI_NO_SCREEN_RECORDING_PERMISSION",
    },
    12: {
      message: "Accessibility permission is not granted. Please enable it in System Settings > Privacy & Security > Accessibility.",
      code: "SWIFT_CLI_NO_ACCESSIBILITY_PERMISSION",
    },
    13: { message: "Invalid display ID provided for capture.", code: "SWIFT_CLI_INVALID_DISPLAY_ID" },
    14: { message: "The screen capture could not be created.", code: "SWIFT_CLI_CAPTURE_CREATION_FAILED" },
    15: { message: "The specified window was not found.", code: "SWIFT_CLI_WINDOW_NOT_FOUND" },
    16: { message: "Failed to capture the specified window.", code: "SWIFT_CLI_WINDOW_CAPTURE_FAILED" },
    17: {
      message: "Failed to write the capture to a file. This is often a file permissions issue. Please ensure the application has permissions to write to the destination directory.",
      code: "SWIFT_CLI_FILE_WRITE_ERROR",
    },
    18: {
      message: "The specified application could not be found or is not running.",
      code: "SWIFT_CLI_APP_NOT_FOUND",
    },
    19: { message: "The specified window index is invalid.", code: "SWIFT_CLI_INVALID_WINDOW_INDEX" },
    20: { message: "Invalid argument provided to the Swift CLI.", code: "SWIFT_CLI_INVALID_ARGUMENT" },
  };
  return errorCodeMap[exitCode] || { message: defaultMessage, code: "SWIFT_CLI_EXECUTION_ERROR" };
}

export async function executeSwiftCli(
  args: string[],
  logger: Logger,
): Promise<SwiftCliResponse> {
  let cliPath: string;
  try {
    cliPath = getInitializedSwiftCliPath(logger);
  } catch (error) {
    // Error already logged by getInitializedSwiftCliPath
    return {
      success: false,
      error: {
        message: (error as Error).message,
        code: "SWIFT_CLI_PATH_INIT_ERROR",
        details: (error as Error).stack,
      },
    };
  }

  // Always add --json-output flag
  const fullArgs = [...args, "--json-output"];

  logger.debug({ command: cliPath, args: fullArgs }, "Executing Swift CLI");

  return new Promise((resolve) => {
    const process = spawn(cliPath, fullArgs);

    let stdout = "";
    let stderr = "";

    process.stdout.on("data", (data: Buffer | string) => {
      stdout += data.toString();
    });

    process.stderr.on("data", (data: Buffer | string) => {
      const stderrData = data.toString();
      stderr += stderrData;
      // Log stderr immediately as it comes in
      logger.warn({ swift_stderr: stderrData.trim() }, "[SwiftCLI-stderr]");
    });

    process.on("close", (exitCode: number | null) => {
      logger.debug(
        { exitCode, stdout: stdout.slice(0, 200) },
        "Swift CLI completed",
      );

      if (exitCode !== 0 || !stdout.trim()) {
        logger.error(
          { exitCode, stdout, stderr },
          "Swift CLI execution failed",
        );

        const { message, code } = mapExitCodeToErrorMessage(exitCode || 1, stderr);
        const errorDetails =
          stderr.trim() && stdout.trim()
            ? `Stdout: ${stdout.trim()}`
            : stderr.trim() || stdout.trim() || "No output received";

        resolve({
          success: false,
          error: {
            message,
            code,
            details: errorDetails,
          },
        });
        return;
      }

      try {
        const response = JSON.parse(stdout) as SwiftCliResponse;

        // Log debug messages from Swift CLI
        if (response.debug_logs && Array.isArray(response.debug_logs)) {
          response.debug_logs.forEach((entry) => {
            logger.debug({ backend: "swift", swift_log: entry });
          });
        }

        resolve(response);
      } catch (parseError) {
        logger.error(
          { parseError, stdout },
          "Failed to parse Swift CLI JSON output",
        );
        resolve({
          success: false,
          error: {
            message: "Invalid JSON response from Swift CLI",
            code: "SWIFT_CLI_INVALID_OUTPUT",
            details: stdout.slice(0, 500),
          },
        });
      }
    });

    process.on("error", (error: Error) => {
      logger.error({ error }, "Failed to spawn Swift CLI process");
      resolve({
        success: false,
        error: {
          message: `Failed to execute Swift CLI: ${error.message}`,
          code: "SWIFT_CLI_SPAWN_ERROR",
          details: error.toString(),
        },
      });
    });
  });
}

export async function readImageAsBase64(imagePath: string): Promise<string> {
  const buffer = await fsPromises.readFile(imagePath);
  return buffer.toString("base64");
}

// Simple execution function for basic commands without logger dependency
export async function execPeekaboo(
  args: string[],
  packageRootDir: string,
  options: { expectSuccess?: boolean } = {},
): Promise<{ success: boolean; data?: string; error?: string }> {
  const cliPath = process.env.PEEKABOO_CLI_PATH || path.resolve(packageRootDir, "peekaboo");

  return new Promise((resolve) => {
    const process = spawn(cliPath, args);
    let stdout = "";
    let stderr = "";

    process.stdout.on("data", (data) => {
      stdout += data.toString();
    });

    process.stderr.on("data", (data) => {
      stderr += data.toString();
    });

    process.on("close", (code) => {
      const success = code === 0;
      if (options.expectSuccess !== false && !success) {
        resolve({ success: false, error: stderr || stdout });
      } else {
        resolve({ success, data: stdout, error: stderr });
      }
    });

    process.on("error", (err) => {
      resolve({ success: false, error: err.message });
    });
  });
}
