import { promises } from 'fs';
import { extname, basename, dirname, join } from 'path';
import { createHash } from 'crypto';
import sharp from 'sharp';
import { bo as getStorageProvider, as as config } from '../nitro/nitro.mjs';

const IMAGE_MIME_TYPES = /* @__PURE__ */ new Set([
  "image/jpeg",
  "image/png",
  "image/webp",
  "image/avif",
  "image/gif",
  "image/tiff"
]);
const FORMAT_TO_MIME = {
  jpeg: "image/jpeg",
  jpg: "image/jpeg",
  png: "image/png",
  webp: "image/webp",
  avif: "image/avif",
  gif: "image/gif",
  tiff: "image/tiff"
};
const MIME_TO_FORMAT = {
  "image/jpeg": "jpeg",
  "image/png": "png",
  "image/webp": "webp",
  "image/avif": "avif"
};
const MAX_DIMENSION = 16384;
const MIN_DIMENSION = 1;
const DEFAULT_QUALITY = 80;
function isProcessableImage(mimeType) {
  return IMAGE_MIME_TYPES.has(mimeType);
}
async function getImageMetadata(buffer) {
  var _a, _b, _c, _d, _e, _f;
  const metadata = await sharp(buffer).metadata();
  return {
    width: (_a = metadata.width) != null ? _a : 0,
    height: (_b = metadata.height) != null ? _b : 0,
    format: (_c = metadata.format) != null ? _c : "unknown",
    hasAlpha: (_d = metadata.hasAlpha) != null ? _d : false,
    space: (_e = metadata.space) != null ? _e : "unknown",
    channels: (_f = metadata.channels) != null ? _f : 0,
    size: buffer.length
  };
}
async function transformImage(buffer, options) {
  var _a, _b, _c, _d, _e, _f, _g, _h;
  const { width, height, fit = "cover", format } = options;
  const quality = (_a = options.quality) != null ? _a : DEFAULT_QUALITY;
  const shouldResize = width !== void 0 || height !== void 0;
  const shouldEncode = shouldResize || format !== void 0 || options.quality !== void 0;
  if (!shouldEncode) {
    const metadata = await sharp(buffer).metadata();
    const originalFormat = (_b = metadata.format) != null ? _b : "unknown";
    return {
      buffer,
      mimeType: formatToMime(originalFormat),
      width: (_c = metadata.width) != null ? _c : 0,
      height: (_d = metadata.height) != null ? _d : 0,
      size: buffer.length,
      format: originalFormat
    };
  }
  let pipeline = sharp(buffer);
  if (shouldResize) {
    const resizeOptions = {
      width: width ? clampDimension(width) : void 0,
      height: height ? clampDimension(height) : void 0,
      fit,
      withoutEnlargement: true
    };
    if (fit === "cover" && options.focalPoint) {
      resizeOptions.position = focalPointToGravity(options.focalPoint);
    }
    pipeline = pipeline.resize(resizeOptions);
  }
  if (format) {
    pipeline = applyFormat(pipeline, format, quality);
  } else {
    const meta = await sharp(buffer).metadata();
    const originalFormat = meta.format;
    if (originalFormat && originalFormat in MIME_TO_FORMAT) {
      pipeline = applyFormat(pipeline, MIME_TO_FORMAT[`image/${originalFormat}`], quality);
    }
  }
  const outputBuffer = await pipeline.toBuffer();
  const outputMeta = await sharp(outputBuffer).metadata();
  const outputFormat = (_e = format != null ? format : outputMeta.format) != null ? _e : "jpeg";
  const mimeType = (_f = FORMAT_TO_MIME[outputFormat]) != null ? _f : "image/jpeg";
  return {
    buffer: outputBuffer,
    mimeType,
    width: (_g = outputMeta.width) != null ? _g : 0,
    height: (_h = outputMeta.height) != null ? _h : 0,
    size: outputBuffer.length,
    format: outputFormat
  };
}
async function generateVariants(buffer, configs) {
  const results = /* @__PURE__ */ new Map();
  const entries = await Promise.all(
    configs.map(async (config) => {
      var _a, _b;
      const result = await transformImage(buffer, {
        width: config.width,
        height: config.height,
        fit: (_a = config.fit) != null ? _a : "cover",
        format: config.format,
        quality: (_b = config.quality) != null ? _b : DEFAULT_QUALITY,
        focalPoint: config.focalPoint
      });
      return [config.name, result];
    })
  );
  for (const [name, result] of entries) {
    results.set(name, result);
  }
  return results;
}
function normalizeImage(buffer) {
  return sharp(buffer).rotate().toBuffer();
}
function formatToMime(format) {
  var _a;
  return (_a = FORMAT_TO_MIME[format]) != null ? _a : "application/octet-stream";
}
function clampDimension(value) {
  return Math.max(MIN_DIMENSION, Math.min(MAX_DIMENSION, Math.round(value)));
}
function focalPointToGravity(fp) {
  var _a;
  const x = Math.max(0, Math.min(1, fp.x));
  const y = Math.max(0, Math.min(1, fp.y));
  const horizontal = x < 0.33 ? "left" : x > 0.66 ? "right" : "center";
  const vertical = y < 0.33 ? "top" : y > 0.66 ? "bottom" : "center";
  const gravityMap = {
    "top-left": "northwest",
    "top-center": "north",
    "top-right": "northeast",
    "center-left": "west",
    "center-center": "centre",
    "center-right": "east",
    "bottom-left": "southwest",
    "bottom-center": "south",
    "bottom-right": "southeast"
  };
  return (_a = gravityMap[`${vertical}-${horizontal}`]) != null ? _a : "centre";
}
function applyFormat(pipeline, format, quality) {
  const clampedQuality = Math.max(1, Math.min(100, quality));
  switch (format) {
    case "jpeg":
      return pipeline.jpeg({ quality: clampedQuality, mozjpeg: true });
    case "png":
      return pipeline.png({ quality: clampedQuality });
    case "webp":
      return pipeline.webp({ quality: clampedQuality });
    case "avif":
      return pipeline.avif({ quality: clampedQuality });
    default:
      return pipeline;
  }
}

const VARIANTS_SUBFOLDER = "_variants";
const TRANSFORM_CACHE_SUBFOLDER = "_transforms";
async function generateMediaVariants(sourceBuffer, sourcePath, mimeType, options = {}) {
  if (!isProcessableImage(mimeType)) {
    return null;
  }
  const shouldGenerate = config.get("media.generateThumbnails", true);
  if (!shouldGenerate) {
    return null;
  }
  const thumbnailSizes = config.get(
    "media.thumbnailSizes",
    {
      small: { width: 150, height: 150 },
      medium: { width: 300, height: 300 },
      large: { width: 600, height: 600 }
    }
  );
  const variantConfigs = Object.entries(thumbnailSizes).map(([name, size]) => {
    var _a;
    return {
      name,
      width: size.width,
      height: size.height,
      fit: "cover",
      focalPoint: (_a = options.focalPoint) != null ? _a : void 0
    };
  });
  const results = await generateVariants(sourceBuffer, variantConfigs);
  const storage = getStorageProvider();
  const variants = {};
  const ext = extname(sourcePath);
  const nameWithoutExt = basename(sourcePath, ext);
  const sourceDir = dirname(sourcePath);
  const variantsDir = join(sourceDir, VARIANTS_SUBFOLDER);
  const uploadedVariantPaths = [];
  try {
    for (const [name, result] of results) {
      const variantFilename = buildVariantFilename(
        nameWithoutExt,
        name,
        ext,
        options.variantSuffix
      );
      const variantPath = join(variantsDir, variantFilename);
      await storage.upload(result.buffer, {
        filename: variantFilename,
        folder: variantsDir,
        mimeType: result.mimeType,
        allowedMimeTypes: [result.mimeType]
      });
      uploadedVariantPaths.push(variantPath.replace(/\\/g, "/"));
      variants[name] = {
        name,
        width: result.width,
        height: result.height,
        size: result.size,
        format: result.format,
        mimeType: result.mimeType,
        path: variantPath.replace(/\\/g, "/"),
        url: storage.getUrl(variantPath)
      };
    }
  } catch (error) {
    await Promise.all(
      uploadedVariantPaths.map(async (path) => {
        try {
          await storage.delete({ path });
        } catch {
        }
      })
    );
    throw error;
  }
  return { variants };
}
async function deleteMediaVariants(formats) {
  if (!(formats == null ? void 0 : formats.variants)) {
    return;
  }
  const storage = getStorageProvider();
  const deletions = Object.values(formats.variants).map(async (variant) => {
    try {
      await storage.delete({ path: variant.path });
    } catch {
    }
  });
  await Promise.all(deletions);
}
async function getOrCreateTransform(mediaId, sourcePath, mimeType, options) {
  if (!isProcessableImage(mimeType)) {
    return null;
  }
  const cacheKey = buildTransformCacheKey(mediaId, options);
  const cached = await readTransformCache(cacheKey);
  if (cached) {
    return cached;
  }
  const storage = getStorageProvider();
  const sourceBuffer = await storage.getFile(sourcePath);
  const result = await transformImage(sourceBuffer, options);
  writeTransformCache(cacheKey, result).catch(() => {
  });
  return result;
}
async function invalidateTransformCache(mediaId) {
  const cacheDir = getTransformCacheDir(mediaId);
  try {
    await promises.rm(cacheDir, { recursive: true, force: true });
  } catch {
  }
}
function getTransformCacheDir(mediaId) {
  const storage = getStorageProvider();
  const storageConfig = storage.getType() === "local" ? "uploads" : ".cache/transforms";
  return join(process.cwd(), storageConfig, TRANSFORM_CACHE_SUBFOLDER, String(mediaId));
}
function buildVariantFilename(nameWithoutExt, variantName, ext, variantSuffix) {
  const safeSuffix = variantSuffix == null ? void 0 : variantSuffix.replace(/[^a-zA-Z0-9_-]/g, "");
  return safeSuffix ? `${nameWithoutExt}__${variantName}__${safeSuffix}${ext}` : `${nameWithoutExt}__${variantName}${ext}`;
}
function buildTransformCacheKey(mediaId, options) {
  const parts = [
    `id=${mediaId}`,
    options.width ? `w=${options.width}` : "",
    options.height ? `h=${options.height}` : "",
    options.fit ? `f=${options.fit}` : "",
    options.format ? `fmt=${options.format}` : "",
    options.quality ? `q=${options.quality}` : "",
    options.focalPoint ? `fp=${options.focalPoint.x},${options.focalPoint.y}` : ""
  ].filter(Boolean).join("&");
  const hash = createHash("sha256").update(parts).digest("hex").slice(0, 16);
  return `${mediaId}/${hash}`;
}
async function readTransformCache(cacheKey) {
  const storage = getStorageProvider();
  const storageConfig = storage.getType() === "local" ? "uploads" : ".cache/transforms";
  const metaPath = join(
    process.cwd(),
    storageConfig,
    TRANSFORM_CACHE_SUBFOLDER,
    `${cacheKey}.meta.json`
  );
  const dataPath = join(process.cwd(), storageConfig, TRANSFORM_CACHE_SUBFOLDER, `${cacheKey}.bin`);
  try {
    const [metaRaw, buffer] = await Promise.all([
      promises.readFile(metaPath, "utf-8"),
      promises.readFile(dataPath)
    ]);
    const meta = JSON.parse(metaRaw);
    return {
      buffer,
      mimeType: meta.mimeType,
      width: meta.width,
      height: meta.height,
      size: buffer.length,
      format: meta.format
    };
  } catch {
    return null;
  }
}
async function writeTransformCache(cacheKey, result) {
  const storage = getStorageProvider();
  const storageConfig = storage.getType() === "local" ? "uploads" : ".cache/transforms";
  const metaPath = join(
    process.cwd(),
    storageConfig,
    TRANSFORM_CACHE_SUBFOLDER,
    `${cacheKey}.meta.json`
  );
  const dataPath = join(process.cwd(), storageConfig, TRANSFORM_CACHE_SUBFOLDER, `${cacheKey}.bin`);
  await promises.mkdir(dirname(metaPath), { recursive: true });
  const meta = {
    mimeType: result.mimeType,
    width: result.width,
    height: result.height,
    format: result.format
  };
  await Promise.all([
    promises.writeFile(metaPath, JSON.stringify(meta)),
    promises.writeFile(dataPath, result.buffer)
  ]);
}
function parseFocalPoint(value) {
  if (!value || typeof value !== "object") {
    return void 0;
  }
  const fp = value;
  const x = typeof fp.x === "number" ? fp.x : void 0;
  const y = typeof fp.y === "number" ? fp.y : void 0;
  if (x === void 0 || y === void 0) {
    return void 0;
  }
  if (x < 0 || x > 1 || y < 0 || y > 1) {
    return void 0;
  }
  return { x, y };
}
function validateFocalPoint(value) {
  if (value === null) {
    return null;
  }
  if (!value || typeof value !== "object") {
    throw new Error("focalPoint must be an object with x and y properties (0-1 range) or null");
  }
  const fp = value;
  if (typeof fp.x !== "number" || typeof fp.y !== "number") {
    throw new Error("focalPoint.x and focalPoint.y must be numbers");
  }
  if (fp.x < 0 || fp.x > 1 || fp.y < 0 || fp.y > 1) {
    throw new Error("focalPoint.x and focalPoint.y must be between 0 and 1");
  }
  return { x: fp.x, y: fp.y };
}

export { isProcessableImage as a, getOrCreateTransform as b, getImageMetadata as c, deleteMediaVariants as d, generateMediaVariants as g, invalidateTransformCache as i, normalizeImage as n, parseFocalPoint as p, validateFocalPoint as v };
//# sourceMappingURL=media-variants.mjs.map
