import {
  validatePack
} from "./chunk-2D5KFYGX.js";
import {
  computePackHash
} from "./chunk-EXINSFZE.js";
import {
  WORKSPACE_FILE_NAME
} from "./chunk-HANJXVKW.js";
import {
  WU_OPTIONS,
  createWUParser,
  runCLI
} from "./chunk-2GXVIN57.js";
import {
  ErrorCodes,
  createError
} from "./chunk-RXRKBBSM.js";

// src/pack-install.ts
import { createHash } from "crypto";
import { existsSync, mkdirSync } from "fs";
import { readFile, writeFile } from "fs/promises";
import { join, resolve } from "path";
import { execFile } from "child_process";
import { tmpdir } from "os";
import { promisify } from "util";
import YAML from "yaml";
var execFileAsync = promisify(execFile);
var LOG_PREFIX = "[pack:install]";
var PACK_SOURCE_VALUES = ["local", "git", "registry"];
var UTF8 = "utf-8";
var DEFAULT_REGISTRY_URL = "https://registry.lumenflow.dev";
var REGISTRY_PACKS_API_PATH = "/api/registry/packs";
var SHA256_INTEGRITY_PATTERN = /^sha256:[a-f0-9]{64}$/;
async function readWorkspaceFile(workspaceRoot) {
  const workspacePath = join(workspaceRoot, WORKSPACE_FILE_NAME);
  if (!existsSync(workspacePath)) {
    throw createError(
      ErrorCodes.WORKSPACE_NOT_FOUND,
      `${WORKSPACE_FILE_NAME} not found at ${workspacePath}. Run "lumenflow init" to create a workspace first.`
    );
  }
  const content = await readFile(workspacePath, UTF8);
  const parsed = YAML.parse(content);
  if (!parsed || typeof parsed !== "object") {
    throw createError(
      ErrorCodes.WORKSPACE_MALFORMED,
      `${WORKSPACE_FILE_NAME} is empty or malformed.`
    );
  }
  if (!Array.isArray(parsed.packs)) {
    parsed.packs = [];
  }
  return parsed;
}
async function writeWorkspaceFile(workspaceRoot, data) {
  const workspacePath = join(workspaceRoot, WORKSPACE_FILE_NAME);
  const content = YAML.stringify(data, { lineWidth: 120 });
  await writeFile(workspacePath, content, UTF8);
}
function buildPackPin(options) {
  const pin = {
    id: options.packId,
    version: options.version,
    integrity: options.integrity,
    source: options.source
  };
  if (options.url) {
    pin.url = options.url;
  }
  if (options.registryUrl) {
    pin.registry_url = options.registryUrl;
  }
  return pin;
}
function upsertPackPin(packs, newPin) {
  const existingIndex = packs.findIndex((p) => p.id === newPin.id);
  if (existingIndex >= 0) {
    const updated = [...packs];
    updated[existingIndex] = newPin;
    return updated;
  }
  return [...packs, newPin];
}
async function installPack(options) {
  const { workspaceRoot, packId, source, version, url, registryUrl, packRoot } = options;
  const absolutePackRoot = resolve(packRoot);
  let workspace;
  try {
    workspace = await readWorkspaceFile(workspaceRoot);
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    return { success: false, error: message };
  }
  let validation;
  try {
    validation = await validatePack({ packRoot: absolutePackRoot });
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    return { success: false, error: `Pack validation failed: ${message}` };
  }
  if (!validation.allPassed) {
    return {
      success: false,
      error: `Pack validation failed for "${packId}". Run pack:validate --pack-root ${absolutePackRoot} for details.`,
      validation
    };
  }
  let integrity;
  try {
    integrity = await computePackHash({ packRoot: absolutePackRoot });
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    return { success: false, error: `Integrity hash computation failed: ${message}` };
  }
  const pin = buildPackPin({ packId, version, source, integrity, url, registryUrl });
  workspace.packs = upsertPackPin(workspace.packs, pin);
  try {
    await writeWorkspaceFile(workspaceRoot, workspace);
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    return { success: false, error: `Failed to write ${WORKSPACE_FILE_NAME}: ${message}` };
  }
  return {
    success: true,
    integrity,
    validation
  };
}
function buildRegistryTarballUrl(registryUrl, packId, version) {
  const base = registryUrl.replace(/\/+$/, "");
  return `${base}${REGISTRY_PACKS_API_PATH}/${encodeURIComponent(packId)}/versions/${encodeURIComponent(version)}/tarball`;
}
function buildRegistryPackDetailUrl(registryUrl, packId) {
  const base = registryUrl.replace(/\/+$/, "");
  return `${base}${REGISTRY_PACKS_API_PATH}/${encodeURIComponent(packId)}`;
}
function isLocalRegistryHost(hostname) {
  return hostname === "localhost" || hostname === "127.0.0.1" || hostname === "::1";
}
function validateRegistryUrl(registryUrl) {
  let parsed;
  try {
    parsed = new URL(registryUrl);
  } catch {
    return { valid: false, error: `Invalid --registry-url: "${registryUrl}"` };
  }
  if (parsed.username || parsed.password) {
    return {
      valid: false,
      error: "Registry URL must not include username/password credentials"
    };
  }
  const usesHttps = parsed.protocol === "https:";
  const allowsInsecureLocalhost = parsed.protocol === "http:" && isLocalRegistryHost(parsed.hostname);
  if (!usesHttps && !allowsInsecureLocalhost) {
    return {
      valid: false,
      error: "Registry URL must use HTTPS unless host is localhost, 127.0.0.1, or ::1"
    };
  }
  return { valid: true, normalizedUrl: parsed.toString().replace(/\/+$/, "") };
}
function parsePackDetailResponse(data) {
  if (!data || typeof data !== "object") {
    return null;
  }
  const maybeRecord = data;
  const packRaw = "pack" in maybeRecord && maybeRecord.pack && typeof maybeRecord.pack === "object" ? maybeRecord.pack : maybeRecord;
  const latestVersion = typeof packRaw.latestVersion === "string" ? packRaw.latestVersion : void 0;
  const versionsRaw = packRaw.versions;
  if (!Array.isArray(versionsRaw)) {
    return null;
  }
  const versions = [];
  for (const entry of versionsRaw) {
    if (!entry || typeof entry !== "object") {
      continue;
    }
    const maybeVersion = entry.version;
    const maybeIntegrity = entry.integrity;
    if (typeof maybeVersion === "string" && typeof maybeIntegrity === "string") {
      versions.push({ version: maybeVersion, integrity: maybeIntegrity });
    }
  }
  return { latestVersion, versions };
}
async function resolveRegistryIntegrity(options) {
  const registryValidation = validateRegistryUrl(options.registryUrl);
  if (!registryValidation.valid || !registryValidation.normalizedUrl) {
    return {
      success: false,
      error: registryValidation.error ?? `Invalid registry URL: ${options.registryUrl}`
    };
  }
  const detailUrl = buildRegistryPackDetailUrl(registryValidation.normalizedUrl, options.packId);
  let detailResponse;
  try {
    detailResponse = await options.fetchFn(detailUrl);
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    return {
      success: false,
      error: `Failed to fetch registry metadata for "${options.packId}": ${message}`
    };
  }
  if (!detailResponse.ok) {
    const body = await detailResponse.text().catch(() => "No response body");
    return {
      success: false,
      error: `Registry metadata lookup returned ${String(detailResponse.status)} ${detailResponse.statusText} for "${options.packId}": ${body}`
    };
  }
  let detailJson;
  try {
    detailJson = await detailResponse.json();
  } catch {
    return {
      success: false,
      error: `Failed to parse registry metadata JSON for "${options.packId}"`
    };
  }
  const parsedDetail = parsePackDetailResponse(detailJson);
  if (!parsedDetail || parsedDetail.versions.length === 0) {
    return {
      success: false,
      error: `Registry metadata for "${options.packId}" did not include any versions`
    };
  }
  const targetVersion = options.version === "latest" ? parsedDetail.latestVersion ?? parsedDetail.versions[parsedDetail.versions.length - 1]?.version : options.version;
  if (!targetVersion) {
    return {
      success: false,
      error: `Registry metadata for "${options.packId}" is missing latestVersion`
    };
  }
  const versionEntry = parsedDetail.versions.find((entry) => entry.version === targetVersion);
  if (!versionEntry) {
    return {
      success: false,
      error: `Version "${targetVersion}" not found for pack "${options.packId}"`
    };
  }
  if (!SHA256_INTEGRITY_PATTERN.test(versionEntry.integrity)) {
    return {
      success: false,
      error: `Registry returned invalid integrity for "${options.packId}@${targetVersion}"`
    };
  }
  return {
    success: true,
    resolvedVersion: versionEntry.version,
    resolvedIntegrity: versionEntry.integrity
  };
}
function verifySha256Integrity(buffer, expectedIntegrity) {
  const data = Buffer.from(buffer);
  const actualHex = createHash("sha256").update(data).digest("hex");
  const actualIntegrity = `sha256:${actualHex}`;
  return {
    valid: actualIntegrity === expectedIntegrity,
    actual: actualIntegrity,
    expected: expectedIntegrity
  };
}
async function extractTarball(tarballPath, targetDir) {
  mkdirSync(targetDir, { recursive: true });
  await execFileAsync("tar", ["-xzf", tarballPath, "-C", targetDir]);
}
async function installPackFromRegistry(options) {
  const { workspaceRoot, packId, version, registryUrl, integrity, fetchFn } = options;
  const registryValidation = validateRegistryUrl(registryUrl);
  if (!registryValidation.valid || !registryValidation.normalizedUrl) {
    return {
      success: false,
      error: registryValidation.error ?? `Invalid registry URL: ${registryUrl}`
    };
  }
  const normalizedRegistryUrl = registryValidation.normalizedUrl;
  let resolvedVersion = version;
  let resolvedIntegrity = integrity?.trim() ?? "";
  if (resolvedIntegrity.length > 0 && !SHA256_INTEGRITY_PATTERN.test(resolvedIntegrity)) {
    return {
      success: false,
      error: `Invalid integrity format for "${packId}@${resolvedVersion}". Expected sha256:<hex>`
    };
  }
  if (resolvedIntegrity.length === 0) {
    const integrityResult = await resolveRegistryIntegrity({
      packId,
      version: resolvedVersion,
      registryUrl: normalizedRegistryUrl,
      fetchFn
    });
    if (!integrityResult.success || !integrityResult.resolvedIntegrity || !integrityResult.resolvedVersion) {
      return {
        success: false,
        error: integrityResult.error ?? `Failed to resolve integrity for "${packId}@${resolvedVersion}"`
      };
    }
    resolvedVersion = integrityResult.resolvedVersion;
    resolvedIntegrity = integrityResult.resolvedIntegrity;
  }
  const tarballUrl = buildRegistryTarballUrl(normalizedRegistryUrl, packId, resolvedVersion);
  let response;
  try {
    response = await fetchFn(tarballUrl);
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    return {
      success: false,
      error: `Registry fetch failed for "${packId}@${resolvedVersion}": ${message}`
    };
  }
  if (!response.ok) {
    const body = await response.text().catch(() => "No response body");
    return {
      success: false,
      error: `Registry returned ${String(response.status)} ${response.statusText} for "${packId}@${resolvedVersion}": ${body}`
    };
  }
  let tarballBuffer;
  try {
    tarballBuffer = await response.arrayBuffer();
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    return {
      success: false,
      error: `Failed to read tarball response body: ${message}`
    };
  }
  const integrityCheck = verifySha256Integrity(tarballBuffer, resolvedIntegrity);
  if (!integrityCheck.valid) {
    return {
      success: false,
      error: `Integrity mismatch for "${packId}@${resolvedVersion}". Expected: ${integrityCheck.expected}, Got: ${integrityCheck.actual}`
    };
  }
  const tempBase = join(tmpdir(), `lumenflow-registry-install-${packId}-${Date.now()}`);
  const tarballPath = join(tempBase, `${packId}-${resolvedVersion}.tar.gz`);
  const extractDir = join(tempBase, "pack");
  try {
    mkdirSync(tempBase, { recursive: true });
    await writeFile(tarballPath, Buffer.from(tarballBuffer));
    await extractTarball(tarballPath, extractDir);
  } catch (err) {
    const message = err instanceof Error ? err.message : String(err);
    return {
      success: false,
      error: `Failed to extract tarball for "${packId}@${resolvedVersion}": ${message}`
    };
  }
  return installPack({
    workspaceRoot,
    packId,
    source: "registry",
    version: resolvedVersion,
    registryUrl: normalizedRegistryUrl,
    packRoot: extractDir
  });
}
var DEFAULT_PACKS_ROOT = "packages/@lumenflow/packs";
var PACK_INSTALL_OPTIONS = {
  packId: {
    name: "id",
    flags: "--id <packId>",
    description: "Pack ID to install"
  },
  source: {
    name: "source",
    flags: "--source <source>",
    description: "Pack source: local, git, or registry"
  },
  version: {
    name: "version",
    flags: "--version <version>",
    description: "Pack version in semver format"
  },
  url: {
    name: "url",
    flags: "--url <url>",
    description: "Git repository URL (required for source: git)"
  },
  registryUrl: {
    name: "registryUrl",
    flags: "--registry-url <url>",
    description: "Registry base URL (for source: registry)"
  },
  packRoot: {
    name: "packRoot",
    flags: "--pack-root <dir>",
    description: "Direct path to resolved pack directory on disk (overrides default resolution)"
  },
  packsRoot: {
    name: "packsRoot",
    flags: "--packs-root <dir>",
    description: `Root directory containing packs (default: "${DEFAULT_PACKS_ROOT}")`
  },
  integrity: {
    name: "integrity",
    flags: "--integrity <hash>",
    description: 'Expected SHA-256 integrity hash in "sha256:<hex>" format (optional)'
  }
};
function resolvePackRootFromCli(options) {
  if (options.directPackRoot) {
    return resolve(options.directPackRoot);
  }
  return resolve(options.packsRoot, options.packId);
}
async function main() {
  const opts = createWUParser({
    name: "pack-install",
    description: "Install a LumenFlow domain pack into workspace.yaml",
    options: [
      PACK_INSTALL_OPTIONS.packId,
      PACK_INSTALL_OPTIONS.source,
      PACK_INSTALL_OPTIONS.version,
      PACK_INSTALL_OPTIONS.url,
      PACK_INSTALL_OPTIONS.registryUrl,
      PACK_INSTALL_OPTIONS.packRoot,
      PACK_INSTALL_OPTIONS.packsRoot,
      PACK_INSTALL_OPTIONS.integrity,
      WU_OPTIONS.force
    ],
    required: ["id", "source", "version"]
  });
  const packId = opts.id;
  const source = opts.source;
  const version = opts.version;
  const url = opts.url;
  const registryUrl = opts.registryUrl;
  const directPackRoot = opts.packRoot;
  const packsRoot = opts.packsRoot ?? DEFAULT_PACKS_ROOT;
  if (!PACK_SOURCE_VALUES.includes(source)) {
    console.error(
      `${LOG_PREFIX} Error: Invalid source "${source}". Must be one of: ${PACK_SOURCE_VALUES.join(", ")}`
    );
    process.exit(1);
  }
  if (source === "git" && !url) {
    console.error(`${LOG_PREFIX} Error: --url is required when --source is "git"`);
    process.exit(1);
  }
  const workspaceRoot = resolve(".");
  console.log(`${LOG_PREFIX} Installing pack "${packId}" v${version} (source: ${source})...`);
  let result;
  if (source === "registry") {
    const resolvedRegistryUrl = registryUrl ?? DEFAULT_REGISTRY_URL;
    const integrityFlag = opts.integrity;
    const registryValidation = validateRegistryUrl(resolvedRegistryUrl);
    if (!registryValidation.valid || !registryValidation.normalizedUrl) {
      console.error(`${LOG_PREFIX} Error: ${registryValidation.error}`);
      process.exit(1);
    }
    result = await installPackFromRegistry({
      workspaceRoot,
      packId,
      version,
      registryUrl: registryValidation.normalizedUrl,
      integrity: integrityFlag,
      fetchFn: globalThis.fetch
    });
  } else {
    const packRoot = resolvePackRootFromCli({ directPackRoot, packId, packsRoot });
    result = await installPack({
      workspaceRoot,
      packId,
      source,
      version,
      url,
      registryUrl,
      packRoot
    });
  }
  if (!result.success) {
    console.error(`${LOG_PREFIX} Installation failed: ${result.error}`);
    process.exit(1);
  }
  console.log(`${LOG_PREFIX} Pack "${packId}" v${version} installed successfully.`);
  console.log(`${LOG_PREFIX} Integrity: ${result.integrity}`);
  console.log(`${LOG_PREFIX} workspace.yaml updated.`);
}
if (import.meta.main) {
  void runCLI(main);
}

export {
  LOG_PREFIX,
  DEFAULT_REGISTRY_URL,
  installPack,
  validateRegistryUrl,
  resolveRegistryIntegrity,
  installPackFromRegistry,
  main
};
