/**
 * هسته تبدیل عکس به مربع ۱:۱ یکدست (بدون import "server-only" تا اسکریپت‌های خط فرمان هم بتوانند از آن استفاده کنند).
 *
 * هدف: همه عکس‌ها در کارت‌های سایت یک شکل باشند؛ مربع، محصول وسط کادر، و زمینه خاکستری یکدست SQUARE_BACKGROUND.
 * - تناسب عکس حفظ می‌شود (بدون کشیدگی و بدون برش) و فضای خالی دو طرف با زمینه پر می‌شود
 * - عکس استودیویی با زمینه روشن یکدست (مثلاً سفید): رنگ‌های عکس کمی تنظیم می‌شوند تا زمینه دقیقاً همان خاکستری
 *   کارت‌ها شود (سفید ۲۵۵ ← ۲۴۵؛ خود محصول فقط حدود ۴٪ تیره‌تر می‌شود که با چشم دیده نمی‌شود)
 * - زمینه شفاف PNG هم همان خاکستری می‌شود
 * - عکس با زمینه تیره یا رنگی یکدست: حاشیه با همان رنگ لبه پر می‌شود تا مرزی دیده نشود
 * - عکس با لبه‌های شلوغ (بدون زمینه یکدست): حاشیه خاکستری
 */
import sharp from "sharp";

/** خاکستری زمینه همه عکس‌ها؛ همان رنگ mist زمینه کارت‌های سایت */
export const SQUARE_BACKGROUND = "#f5f5f3";
const BG_RGB = [0xf5, 0xf5, 0xf3];
export const MAX_PIXELS = 50_000_000; // جلوی فایل‌های «بمب تصویری» را می‌گیرد
/** حداکثر اختلاف رنگ پیکسل‌های لبه (۰ تا ۲۵۵) تا لبه «یکدست» حساب شود */
const EDGE_TOLERANCE = 14;
/** زمینه‌ای که همه کانال‌هایش از این روشن‌تر باشد «روشن» حساب می‌شود و به خاکستری کارت‌ها برده می‌شود */
const LIGHT_EDGE = 215;

const toHex = (rgb: number[]) => "#" + rgb.map((v) => Math.round(v).toString(16).padStart(2, "0")).join("");

export interface SquarePlan {
  /** رنگ حاشیه */
  background: string;
  /** ضریب هر کانال رنگ برای بردن زمینه روشن عکس به خاکستری کارت‌ها */
  scale?: [number, number, number];
}

/** رنگ لبه عکس اگر یکدست باشد؛ وگرنه null */
async function uniformEdgeColor(input: Buffer): Promise<number[] | null> {
  const { data, info } = await sharp(input, { limitInputPixels: MAX_PIXELS, animated: false })
    .rotate()
    .flatten({ background: SQUARE_BACKGROUND })
    .resize(48, 48, { fit: "fill" })
    .removeAlpha()
    .raw()
    .toBuffer({ resolveWithObject: true });
  const n = info.width;
  const px: number[][] = [];
  for (let i = 0; i < n; i++)
    for (const [x, y] of [[i, 0], [i, n - 1], [0, i], [n - 1, i]]) {
      const o = (y * n + x) * info.channels;
      px.push([data[o], data[o + 1], data[o + 2]]);
    }
  // میانه (نه میانگین) تا سایه زیر محصول یا یک لک، رنگ زمینه را تغییر ندهد
  const median = [0, 1, 2].map((c) => px.map((p) => p[c]).sort((a, b) => a - b)[px.length >> 1]);
  // ۸۵٪ پیکسل‌های لبه باید نزدیک این رنگ باشند (سایه نرم زیر محصول مشکلی ندارد)
  const close = px.filter((p) => p.every((v, c) => Math.abs(v - median[c]) <= EDGE_TOLERANCE)).length;
  return close / px.length >= 0.85 ? median : null;
}

/** تصمیم رنگ حاشیه و تنظیم رنگ برای یک عکس */
export async function squarePlanFor(input: Buffer): Promise<SquarePlan> {
  const edge = await uniformEdgeColor(input);
  if (!edge) return { background: SQUARE_BACKGROUND };
  if (edge.every((v) => v >= LIGHT_EDGE)) {
    const scale = edge.map((v, c) => BG_RGB[c] / v) as [number, number, number];
    // زمینه همین الان خاکستری کارت‌هاست: تغییری لازم نیست
    return scale.every((s) => Math.abs(s - 1) < 0.01) ? { background: SQUARE_BACKGROUND } : { background: SQUARE_BACKGROUND, scale };
  }
  return { background: toHex(edge) };
}

/** fit: "contain" یعنی ضلع بلندتر به اندازه کادر می‌رسد و دو طرف ضلع کوتاه‌تر به یک اندازه با زمینه پر می‌شود */
export function squareWebp(input: Buffer, size: number, plan: SquarePlan = { background: SQUARE_BACKGROUND }) {
  let img = sharp(input, { limitInputPixels: MAX_PIXELS, animated: false }) // GIF و TIFF چندفریمی: فقط فریم اول
    .rotate()
    .flatten({ background: plan.background }); // زمینه شفاف ← رنگ حاشیه
  if (plan.scale) img = img.linear(plan.scale, [0, 0, 0]);
  return img
    .resize(size, size, { fit: "contain", position: "centre", background: plan.background, kernel: "lanczos3" })
    .webp({ quality: size >= 1600 ? 80 : 82, effort: 4 })
    .toBuffer();
}
