"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var chunk_6DDIA3ZD_exports = {};
__export(chunk_6DDIA3ZD_exports, {
  EngineError: () => EngineError,
  SchemaEngineCLI: () => SchemaEngineCLI
});
module.exports = __toCommonJS(chunk_6DDIA3ZD_exports);
var import_chunk_PEUHUCS4 = require("./chunk-PEUHUCS4.js");
var import_chunk_SKRR5WT4 = require("./chunk-SKRR5WT4.js");
var import_node_readline = __toESM(require("node:readline"));
var import_debug = __toESM(require("@prisma/debug"));
var import_internals = require("@prisma/internals");
var import_child_process = require("child_process");
var debugRpc = (0, import_debug.default)("prisma:schemaEngine:rpc");
var debugStderr = (0, import_debug.default)("prisma:schemaEngine:stderr");
var debugStdin = (0, import_debug.default)("prisma:schemaEngine:stdin");
var EngineError = class extends Error {
  code;
  constructor(message, code) {
    super(message);
    this.code = code;
  }
};
(0, import_internals.setClassName)(EngineError, "EngineError");
var messageId = 1;
var SchemaEngineCLI = class _SchemaEngineCLI {
  debug;
  child;
  schemaContext;
  datasource;
  listeners = {};
  /**  _All_ the logs from the engine process. */
  messages = [];
  lastRequest;
  /** The fields of the last engine log event with an `ERROR` level. */
  lastError = null;
  initPromise;
  enabledPreviewFeatures;
  extensionConfig;
  baseDir;
  // `isRunning` is set to true when the engine is initialized, and set to false when the engine is stopped
  isRunning = false;
  constructor({
    debug = false,
    schemaContext,
    datasource,
    enabledPreviewFeatures,
    extensions,
    baseDir
  }) {
    this.schemaContext = schemaContext;
    this.datasource = datasource;
    if (debug) {
      import_debug.default.enable("SchemaEngine*");
    }
    this.debug = debug;
    this.enabledPreviewFeatures = enabledPreviewFeatures;
    this.extensionConfig = extensions ? { types: extensions.flatMap((ext) => ext.types) } : void 0;
    this.baseDir = baseDir;
  }
  static setup(input) {
    return Promise.resolve(new _SchemaEngineCLI(input));
  }
  //
  // See JSON-RPC API definition:
  // https://prisma.github.io/prisma-engines/doc/migration_core/json_rpc/index.html
  //
  /**
   * Apply the migrations from the migrations directory to the database.
   * This is the command behind prisma migrate deploy.
   */
  applyMigrations(args) {
    return this.runCommand(this.getRPCPayload("applyMigrations", args));
  }
  /**
   * Create the logical database from the Prisma schema.
   */
  createDatabase(args) {
    return this.runCommand(this.getRPCPayload("createDatabase", args));
  }
  /**
   * Create the next migration in the migrations history.
   * If draft is false and there are no unexecutable steps, it will also apply the newly created migration.
   * Note: This will use the shadow database on the connectors where we need one.
   */
  createMigration(args) {
    return this.runCommand(this.getRPCPayload("createMigration", args));
  }
  /**
   * Execute a database script directly on the specified live database.
   * Note that this may not be defined on all connectors.
   */
  dbExecute(args) {
    return this.runCommand(this.getRPCPayload("dbExecute", args));
  }
  /**
   * Make the Schema engine panic. Only useful to test client error handling.
   */
  debugPanic() {
    return this.runCommand(this.getRPCPayload("debugPanic", void 0));
  }
  /**
   * The method called at the beginning of migrate dev to decide the course of action,
   * based on the current state of the workspace.
   * It acts as a wrapper around diagnoseMigrationHistory.
   * Its role is to interpret the diagnostic output,
   * and translate it to a concrete action to be performed by the CLI.
   */
  devDiagnostic(args) {
    return this.runCommand(this.getRPCPayload("devDiagnostic", args));
  }
  /**
   * Read the contents of the migrations directory and the migrations table, and returns their relative statuses.
   * At this stage, the Schema engine only reads,
   * it does not write to the database nor the migrations directory, nor does it use a shadow database.
   */
  diagnoseMigrationHistory(args) {
    return this.runCommand(this.getRPCPayload("diagnoseMigrationHistory", args));
  }
  /**
   * Make sure the Schema engine can connect to the database from the Prisma schema.
   */
  ensureConnectionValidity(args) {
    return this.runCommand(this.getRPCPayload("ensureConnectionValidity", args));
  }
  /**
   * Development command for migrations.
   * Evaluate the data loss induced by the next migration the engine would generate on the main database.
   */
  evaluateDataLoss(args) {
    return this.runCommand(this.getRPCPayload("evaluateDataLoss", args));
  }
  getDatabaseDescription(schema) {
    return this.runCommand(this.getRPCPayload("getDatabaseDescription", { schema }));
  }
  /**
   * Get the database version for error reporting.
   * If no argument is given, the version of the database associated to the Prisma schema provided
   * in the constructor will be returned.
   */
  getDatabaseVersion(args) {
    return this.runCommand(this.getRPCPayload("getDatabaseVersion", args));
  }
  /**
   * Given a Prisma schema, introspect the database definitions and update the schema with the results.
   * `compositeTypeDepth` is optional, and only required for MongoDB.
   */
  async introspect({
    schema,
    force = false,
    baseDirectoryPath,
    viewsDirectoryPath,
    compositeTypeDepth = -1,
    // cannot be undefined
    namespaces
  }) {
    try {
      const introspectResult = await this.runCommand(
        this.getRPCPayload("introspect", { schema, force, compositeTypeDepth, namespaces, baseDirectoryPath })
      );
      const { views } = introspectResult;
      if (views) {
        await (0, import_chunk_PEUHUCS4.handleViewsIO)({ views, viewsDirectoryPath });
      }
      return introspectResult;
    } finally {
      await this.stop();
    }
  }
  /**
   * Compares two databases schemas from two arbitrary sources,
   * and display the difference as either a human-readable summary,
   * or an executable script that can be passed to dbExecute.
   * Connection to a shadow database is only necessary when either the from or the to params is a migrations directory.
   * Diffs have a direction. Which source is from and which is to matters.
   * The resulting diff should be thought of as a migration from the schema in `args.from` to the schema in `args.to`.
   * By default, we output a human-readable diff. If you want an executable script, pass the "script": true param.
   */
  migrateDiff(args) {
    return this.runCommand(this.getRPCPayload("diff", args));
  }
  /**
   * Mark a migration as applied in the migrations table.
   * There are two possible outcomes:
   * - The migration is already in the table, but in a failed state. In this case, we will mark it as rolled back, then create a new entry.
   * - The migration is not in the table. We will create a new entry in the migrations table. The started_at and finished_at will be the same.
   * If it is already applied, we return a user-facing error.
   */
  markMigrationApplied(args) {
    return this.runCommand(this.getRPCPayload("markMigrationApplied", args));
  }
  /**
   * Mark an existing failed migration as rolled back in the migrations table. It will still be there, but ignored for all purposes except as audit trail.
   */
  markMigrationRolledBack(args) {
    return this.runCommand(this.getRPCPayload("markMigrationRolledBack", args));
  }
  /**
   * Try to make the database empty: no data and no schema.
   * On most connectors, this is implemented by dropping and recreating the database.
   * If that fails (most likely because of insufficient permissions),
   * the engine attempts a “best effort reset” by inspecting the contents of the database and dropping them individually.
   * Drop and recreate the database. The migrations will not be applied, as it would overlap with applyMigrations.
   */
  reset(args) {
    return this.runCommand(this.getRPCPayload("reset", args));
  }
  /**
   * The command behind db push.
   */
  schemaPush(args) {
    return this.runCommand(this.getRPCPayload("schemaPush", args));
  }
  /**
   * SQL introspection that powers TypedSQL feature
   * @param args
   * @returns
   */
  introspectSql(args) {
    return this.runCommand(this.getRPCPayload("introspectSql", args));
  }
  async stop() {
    if (!this.child) {
      return;
    }
    const result = new Promise((resolve) => {
      const timer = setTimeout(
        () => {
          this.child?.kill();
          resolve();
        },
        process.platform === "win32" ? 4e3 : 0
      ).unref();
      this.child.on("exit", () => {
        clearTimeout(timer);
        resolve();
      });
    });
    this.child.stdin?.end();
    this.isRunning = false;
    return result;
  }
  rejectAll(err) {
    Object.entries(this.listeners).map(([id, listener]) => {
      listener(null, err);
      delete this.listeners[id];
    });
  }
  registerCallback(id, callback) {
    this.listeners[id] = callback;
  }
  handleResponse(response) {
    let result;
    try {
      result = JSON.parse(response);
    } catch (e) {
      console.error(`Could not parse Schema engine response: ${response.slice(0, 200)}. Error: ${e.message}`);
    }
    if (result) {
      if (result.id && (result.result !== void 0 || result.error !== void 0)) {
        if (!this.listeners[result.id]) {
          console.error(`Got result for unknown id ${result.id}`);
        }
        if (this.listeners[result.id]) {
          this.listeners[result.id](result);
          delete this.listeners[result.id];
        }
      } else if (result.method) {
        if (result.id !== void 0) {
          if (result.method === "print" && result.params?.content !== void 0) {
            process.stdout.write(result.params.content + "\n");
            const response2 = {
              id: result.id,
              jsonrpc: "2.0",
              result: {}
            };
            this.child.stdin.write(JSON.stringify(response2) + "\n");
          }
        }
      }
    }
  }
  init() {
    if (!this.initPromise) {
      this.initPromise = this.internalInit();
    }
    return this.initPromise;
  }
  internalInit() {
    return new Promise(async (resolve, reject) => {
      try {
        const { PWD, ...processEnv } = process.env;
        const binaryPath = await (0, import_internals.resolveBinary)(import_internals.BinaryType.SchemaEngineBinary);
        debugRpc("starting Schema engine with binary: " + binaryPath);
        const args = [];
        if (this.schemaContext) {
          const schemaArgs = this.schemaContext.schemaFiles.flatMap(([path]) => ["--datamodels", path]);
          args.push(...schemaArgs);
        }
        if (this.datasource) {
          args.push(...["--datasource", JSON.stringify(this.datasource)]);
        }
        if (this.enabledPreviewFeatures && Array.isArray(this.enabledPreviewFeatures) && this.enabledPreviewFeatures.length > 0) {
          args.push(...["--enabled-preview-features", this.enabledPreviewFeatures.join(",")]);
        }
        if (this.extensionConfig) {
          args.push(...["--extension-types", JSON.stringify(this.extensionConfig)]);
        }
        this.child = (0, import_child_process.spawn)(binaryPath, args, {
          cwd: this.baseDir,
          stdio: ["pipe", "pipe", this.debug ? process.stderr : "pipe"],
          env: {
            // The following environment variables can be overridden by the user.
            RUST_LOG: "info",
            RUST_BACKTRACE: "1",
            // Take env values from process.env (will override values set before)
            ...processEnv
          }
        });
        this.isRunning = true;
        this.child.on("error", (err) => {
          console.error("[schema-engine] error: %s", err);
          this.rejectAll(err);
          reject(err);
        });
        this.child.on("exit", (code) => {
          const exitWithErr = (err) => {
            this.rejectAll(err);
            reject(err);
          };
          const processMessages = this.messages.join("\n");
          const engineMessage = this.lastError?.message || processMessages;
          const handlePanic = () => {
            const stackTrace = `[EXIT_PANIC]
${processMessages}
${this.lastError?.backtrace ?? ""}`;
            exitWithErr(new import_internals.RustPanic(serializePanic(engineMessage), stackTrace, this.lastRequest, import_internals.ErrorArea.LIFT_CLI));
          };
          switch (code) {
            case import_internals.SchemaEngineExitCode.Success:
              break;
            case import_internals.SchemaEngineExitCode.Error:
              exitWithErr(new Error(`Error in Schema engine: ${engineMessage}`));
              break;
            case import_internals.SchemaEngineExitCode.Panic:
              handlePanic();
              break;
            // treat unknown error codes as panics
            default:
              handlePanic();
          }
        });
        this.child.stdin.on("error", (err) => {
          debugStdin(err);
        });
        const stderrInterface = import_node_readline.default.createInterface({
          input: this.child.stderr,
          crlfDelay: Infinity
        });
        stderrInterface.on("line", (line) => {
          debugStderr(line);
          try {
            const json = JSON.parse(line);
            this.messages.push(json.fields.message);
            if (json.level === "ERROR") {
              this.lastError = json.fields;
            }
          } catch (e) {
          }
        });
        const stdoutInterface = import_node_readline.default.createInterface({
          input: this.child.stdout,
          crlfDelay: Infinity
        });
        stdoutInterface.on("line", (line) => {
          this.handleResponse(line);
        });
        setImmediate(() => {
          resolve();
        });
      } catch (e) {
        reject(e);
      }
    });
  }
  async runCommand(request) {
    if (process.env.FORCE_PANIC_SCHEMA_ENGINE && request.method !== "getDatabaseVersion") {
      request = this.getRPCPayload("debugPanic", void 0);
    }
    await this.init();
    if (this.child?.killed) {
      throw new Error(`Can't execute ${JSON.stringify(request)} because Schema engine already exited.`);
    }
    return new Promise((resolve, reject) => {
      this.registerCallback(request.id, (response, err) => {
        if (err) {
          return reject(err);
        }
        if (response.result !== void 0) {
          resolve(response.result);
        } else {
          if (response.error) {
            debugRpc(response);
            if (response.error.data?.is_panic) {
              const message = response.error.data?.error?.message ?? response.error.message;
              const stackTrace = `[RESPONSE_ERROR_PANIC]
${response.error.data?.message ?? ""}`;
              reject(
                // Handle error and displays the interactive dialog to send panic error
                new import_internals.RustPanic(message, stackTrace, this.lastRequest, import_internals.ErrorArea.LIFT_CLI)
              );
            } else if (response.error.data?.message) {
              let message = `${(0, import_chunk_SKRR5WT4.red)((0, import_internals.relativizePathInPSLError)(response.error.data.message))}
`;
              if (response.error.data?.error_code) {
                message = (0, import_chunk_SKRR5WT4.red)(`${response.error.data.error_code}

`) + message;
                reject(new EngineError(message, response.error.data.error_code));
              } else {
                reject(new Error(message));
              }
            } else {
              reject(
                new Error(
                  `${(0, import_chunk_SKRR5WT4.red)("Error in RPC")}
 Request: ${JSON.stringify(request, null, 2)}
Response: ${JSON.stringify(
                    response,
                    null,
                    2
                  )}
${response.error.message}
`
                )
              );
            }
          } else {
            reject(new Error(`Got invalid RPC response without .result property: ${JSON.stringify(response)}`));
          }
        }
      });
      if (this.child.stdin.destroyed) {
        throw new Error(`Can't execute ${JSON.stringify(request)} because Schema engine is destroyed.`);
      }
      debugRpc("SENDING RPC CALL", JSON.stringify(request));
      this.child.stdin.write(JSON.stringify(request) + "\n");
      this.lastRequest = request;
    });
  }
  getRPCPayload(method, params) {
    return {
      id: messageId++,
      jsonrpc: "2.0",
      method,
      params: params ? {
        ...params
      } : void 0
    };
  }
};
function serializePanic(log) {
  return `${(0, import_chunk_SKRR5WT4.red)((0, import_chunk_SKRR5WT4.bold)("Error in Schema engine.\nReason: "))}${log}
`;
}
