/**
 * @license
 * Copyright 2022-2026 Matter.js Authors
 * SPDX-License-Identifier: Apache-2.0
 */
import Dockerode from "dockerode";
import { finished } from "node:stream/promises";
import { base64Of } from "../util/text.js";
import { DockerError, NonZeroExitError } from "./errors.js";
import { Image } from "./image.js";
import { Terminal } from "./terminal.js";
function Container(docker, input) {
  if (input instanceof Dockerode.Container) {
    return adaptContainer(docker, input);
  }
  return createContainer(docker, input);
}
async function createContainer(docker, config) {
  const dockerodeConfig = configureContainer(config);
  const ct = await DockerError.adapt(docker.intf.createContainer(dockerodeConfig));
  return adaptContainer(docker, ct);
}
function configureContainer(options) {
  const createOptions = {
    Image: options.image,
    HostConfig: {
      AutoRemove: options?.autoRemove,
      // Ugh.  With a proper networking config we can perhaps avoid this.  Probably makes sense to run a utility
      // container with avahi
      SecurityOpt: ["apparmor:unconfined"]
    },
    AttachStdout: true,
    AttachStderr: true
  };
  const { name, entrypoint, env, binds, command, openStdin, network, cwd, platform } = options ?? {};
  if (typeof network === "string") {
    createOptions.HostConfig.NetworkMode = network;
  } else if (network) {
    const networks = Array.isArray(network) ? network : [network];
    createOptions.NetworkingConfig = {
      EndpointsConfig: Object.fromEntries(networks.map((n) => [n.name, {}]))
    };
  }
  if (cwd !== void 0) {
    createOptions.WorkingDir = cwd;
  }
  if (name !== void 0) {
    createOptions.name = name;
  }
  if (entrypoint !== void 0) {
    createOptions.Entrypoint = entrypoint;
  }
  if (env) {
    createOptions.Env = Object.entries(env).map(([k, v]) => `${k}=${v}`);
  }
  if (binds) {
    createOptions.HostConfig.Binds = Object.entries(binds).map(([k, v]) => `${k}:${v}`);
  }
  if (command) {
    createOptions.Cmd = Array.isArray(command) ? command : [command];
  }
  if (openStdin !== false) {
    createOptions.OpenStdin = true;
    createOptions.AttachStdin = true;
    createOptions.StdinOnce = true;
  }
  if (platform) {
    createOptions.platform = platform;
  }
  return createOptions;
}
function adaptContainer(docker, ct) {
  return {
    docker,
    get image() {
      return DockerError.adapt(ct.inspect().then((info) => Image(docker, info.Image)));
    },
    async start() {
      await DockerError.adapt(ct.start());
    },
    async kill() {
      await DockerError.adapt(ct.kill());
    },
    async remove(force) {
      await DockerError.adapt(ct.remove({ force }));
    },
    async attach(terminal, stdin = false) {
      const exited = new Promise((resolve, reject) => {
        this.wait().then(resolve, reject);
      });
      return terminal(
        this.docker,
        await DockerError.adapt(ct.attach({ stream: true, stdin, stdout: true, stderr: true })),
        exited
      );
    },
    async exec(command, terminalOrOptions, options) {
      let terminal;
      if (typeof terminalOrOptions === "object") {
        options = terminalOrOptions;
      } else {
        terminal = terminalOrOptions;
      }
      const { stdin, cwd, env } = options ?? {};
      if (!Array.isArray(command)) {
        command = [command];
      }
      const config = {
        Cmd: command,
        AttachStdin: stdin,
        AttachStdout: true,
        AttachStderr: true
      };
      if (cwd !== void 0) {
        config.WorkingDir = cwd;
      }
      if (env !== void 0) {
        config.Env = Object.entries(env).map(([k, v]) => `${k}=${v}`);
      }
      const exec = await DockerError.adapt(ct.exec(config));
      const stream = await DockerError.adapt(exec.start({ hijack: true, stdin }));
      const exited = new Promise((resolve, reject) => {
        finished(stream).then(() => {
          exec.inspect().then((info) => {
            if (info.ExitCode) {
              reject(new NonZeroExitError(info.ExitCode));
            } else {
              resolve();
            }
          }, reject);
        }, reject);
      });
      if (terminal === void 0) {
        const term = Terminal.Raw(docker, stream, exited);
        await term.consume();
        return;
      }
      return terminal(this.docker, stream, exited);
    },
    async wait(options) {
      const code = (await DockerError.adapt(ct.wait(options))).StatusCode;
      if (code) {
        throw new NonZeroExitError(code);
      }
    },
    async read(path, terminal) {
      const term = await this.exec(["cat", path], terminal ?? Terminal.Line);
      if (terminal === void 0) {
        return await term.consume();
      }
      return term;
    },
    async follow(path, terminal) {
      return this.exec(["tail", "--follow=name", path], terminal ?? Terminal.StdoutLine);
    },
    async execAndRead(command, terminal) {
      const term = await this.exec(command, terminal ?? Terminal.Line);
      if (terminal === void 0) {
        return await term.consume();
      }
      return term;
    },
    async write(path, contents) {
      const terminal = await this.exec(["bash", "-c", `cat - > ${shesc(path)}`], Terminal.Raw, { stdin: true });
      await terminal.write(contents);
      await terminal.close();
    },
    edit(editor, ...paths) {
      return editor(this, paths);
    },
    async delete(path, options) {
      const command = ["rm"];
      if (options?.recursive) {
        command.push("-r");
      }
      if (options?.force) {
        command.push("-f");
      }
      command.push(path);
      await this.exec(command);
    },
    async resolveGlob(glob) {
      const terminal = await this.exec(["bash", "-c", `ls ${glob}`], Terminal.Line);
      const output = await terminal.consume();
      return output.split("\n").filter((line) => line !== "");
    },
    async createPipe(name) {
      await this.exec(["rm", "-f", name]);
      await this.exec(["mkfifo", name]);
    }
  };
}
function shesc(value) {
  return `"$(echo ${base64Of(value)} | base64 -d)"`;
}
export {
  Container
};
//# sourceMappingURL=container.js.map
