const MIN_W_PCT = 8;
const MIN_H_PCT = 5;
const DEFAULT_W_PCT = 62;
const DEFAULT_H_PCT = 46;
const STAGGER_PCT = 4;
const STAGGER_CYCLE = 6;
/** Approximate picture-column width for % sizing (px). */
const PICTURE_COL_WIDTH_PX = 220;

function clampNumber(value, min, max, fallback) {
  const n = Number(value);
  if (!Number.isFinite(n)) return fallback;
  return Math.min(max, Math.max(min, n));
}

export function genCanvasId() {
  return `canvas-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
}

/** Keep an item's geometry within the 0–100 canvas bounds. */
export function clampCanvasItem(item) {
  const w_pct = clampNumber(item?.w_pct, MIN_W_PCT, 100, DEFAULT_W_PCT);
  const h_pct = clampNumber(item?.h_pct, MIN_H_PCT, 100, DEFAULT_H_PCT);
  const x_pct = clampNumber(item?.x_pct, 0, 100 - w_pct, 0);
  const y_pct = clampNumber(item?.y_pct, 0, 100 - h_pct, 0);
  return { ...item, x_pct, y_pct, w_pct, h_pct };
}

/**
 * Resize from the bottom-right corner, keeping top-left fixed.
 * Preserves aspect ratio unless lockAspect is false (e.g. Shift held).
 */
export function resizeCanvasItemFromSe(item, origin, dxPct, dyPct, lockAspect = true) {
  const x_pct = clampNumber(origin.x_pct, 0, 100 - MIN_W_PCT, 0);
  const y_pct = clampNumber(origin.y_pct, 0, 100 - MIN_H_PCT, 0);
  const maxW = Math.max(MIN_W_PCT, 100 - x_pct);
  const maxH = Math.max(MIN_H_PCT, 100 - y_pct);

  let w_pct = origin.w_pct + dxPct;
  let h_pct = origin.h_pct + dyPct;

  if (lockAspect) {
    const aspect =
      origin.h_pct > 0 ? origin.w_pct / origin.h_pct : DEFAULT_W_PCT / DEFAULT_H_PCT;
    const scaleW = origin.w_pct > 0 ? w_pct / origin.w_pct : 1;
    const scaleH = origin.h_pct > 0 ? h_pct / origin.h_pct : 1;
    // Prefer the axis the user moved more (in scale space).
    const scale =
      Math.abs(scaleW - 1) >= Math.abs(scaleH - 1) ? scaleW : scaleH;
    w_pct = origin.w_pct * scale;
    h_pct = origin.h_pct * scale;

    if (w_pct > maxW) {
      w_pct = maxW;
      h_pct = w_pct / aspect;
    }
    if (h_pct > maxH) {
      h_pct = maxH;
      w_pct = h_pct * aspect;
    }
  }

  w_pct = clampNumber(w_pct, MIN_W_PCT, maxW, MIN_W_PCT);
  h_pct = clampNumber(h_pct, MIN_H_PCT, maxH, MIN_H_PCT);

  return { ...item, x_pct, y_pct, w_pct, h_pct };
}

function maxZIndex(items) {
  return (items || []).reduce(
    (max, item) => Math.max(max, Number(item?.z_index) || 0),
    0
  );
}

/** Fallback % size when image dimensions are not known yet. */
export function defaultCanvasItemPercent(cellRowSpan = 1) {
  const span = Math.max(1, Number(cellRowSpan) || 1);
  if (span >= 2) {
    return {
      w_pct: 68,
      h_pct: Math.min(84, 38 + span * 20),
    };
  }
  return { w_pct: DEFAULT_W_PCT, h_pct: DEFAULT_H_PCT };
}

/**
 * Fit a new image into the picture cell using its aspect ratio.
 * Coordinates are cell-local 0–100%.
 */
export function fitCanvasItemPercent(
  naturalWidth,
  naturalHeight,
  { cellRowSpan = 1, rowHeightsPx = [], startRowIndex = 0, colWidthPx = PICTURE_COL_WIDTH_PX } = {}
) {
  const span = Math.max(1, Number(cellRowSpan) || 1);
  const nw = Math.max(1, Number(naturalWidth) || 1);
  const nh = Math.max(1, Number(naturalHeight) || 1);
  const imageAspect = nw / nh;

  const heights = Array.isArray(rowHeightsPx) ? rowHeightsPx : [];
  const cellHeightPx =
    heights.length > 0
      ? heights
          .slice(startRowIndex, startRowIndex + span)
          .reduce((sum, h) => sum + (Number(h) || 0), 0) || 180 * span
      : 180 * span;
  const cellAspect = Math.max(0.2, colWidthPx / cellHeightPx);

  const pad = span > 1 ? 0.9 : 0.86;
  let w_pct;
  let h_pct;

  if (imageAspect >= cellAspect) {
    w_pct = pad * 100;
    h_pct = (w_pct / imageAspect) * cellAspect;
  } else {
    h_pct = pad * 100;
    w_pct = (h_pct * imageAspect) / cellAspect;
  }

  return {
    w_pct: clampNumber(w_pct, MIN_W_PCT, 100, DEFAULT_W_PCT),
    h_pct: clampNumber(h_pct, MIN_H_PCT, 100, DEFAULT_H_PCT),
  };
}

export function measureDataUrlDimensions(dataUrl) {
  return new Promise((resolve) => {
    if (!dataUrl || typeof dataUrl !== "string") {
      resolve({ width: 4, height: 3 });
      return;
    }
    const img = new Image();
    img.onload = () => {
      resolve({
        width: Math.max(1, img.naturalWidth || img.width || 1),
        height: Math.max(1, img.naturalHeight || img.height || 1),
      });
    };
    img.onerror = () => resolve({ width: 4, height: 3 });
    img.src = dataUrl;
  });
}

/** Build a new positioned canvas item, staggered so it does not fully cover existing ones. */
export function createCanvasItem(
  dataUrl,
  existingItems = [],
  rowIndex = null,
  options = {}
) {
  const count = existingItems.length;
  const offset = (count % STAGGER_CYCLE) * STAGGER_PCT;
  const cellRowSpan = Math.max(1, Number(options.cellRowSpan) || 1);
  const rowHeightsPx = Array.isArray(options.rowHeightsPx) ? options.rowHeightsPx : [];
  const startRowIndex = Math.max(0, Number(options.startRowIndex) || 0);
  const hasImageSize =
    Number(options.imageWidth) > 0 && Number(options.imageHeight) > 0;
  const { w_pct, h_pct } = hasImageSize
    ? fitCanvasItemPercent(options.imageWidth, options.imageHeight, {
        cellRowSpan,
        rowHeightsPx,
        startRowIndex,
      })
    : defaultCanvasItemPercent(cellRowSpan);
  const x_pct = 4 + offset;
  const y_pct =
    cellRowSpan > 1
      ? defaultMergedCellYpct(
          cellRowSpan,
          h_pct,
          offset,
          rowHeightsPx,
          startRowIndex
        )
      : 4 + offset;

  return clampCanvasItem({
    id: genCanvasId(),
    image_id: null,
    image_data_url: dataUrl,
    x_pct,
    y_pct,
    w_pct,
    h_pct,
    z_index: maxZIndex(existingItems) + 1,
    label: count + 1,
    ...(rowIndex != null ? { row_index: rowIndex } : {}),
    cell_row_span: cellRowSpan,
  });
}

/**
 * Default vertical position for a new image in a merged picture cell.
 * For a 2-row merge, centers on the seam between the rows; otherwise centers in the cell.
 */
function defaultMergedCellYpct(cellRowSpan, h_pct, staggerOffset, rowHeightsPx, startRowIndex) {
  const nudge = staggerOffset * 0.25;

  if (
    cellRowSpan === 2 &&
    rowHeightsPx.length >= startRowIndex + 2
  ) {
    const heights = rowHeightsPx.slice(startRowIndex, startRowIndex + 2);
    const totalPx = heights.reduce((sum, h) => sum + (Number(h) || 0), 0) || 1;
    const seamPct = ((Number(heights[0]) || 0) / totalPx) * 100;
    return seamPct - h_pct / 2 + nudge;
  }

  return (100 - h_pct) / 2 + nudge;
}

/**
 * Re-sequence labels 1..n following the current label order (stable during
 * drag/resize), collapsing any gaps left by removed items.
 */
export function renumberCanvasLabels(items) {
  const ordered = [...(items || [])]
    .map((item, index) => ({ item, index }))
    .sort((a, b) => {
      const la = Number(a.item?.label);
      const lb = Number(b.item?.label);
      const va = Number.isFinite(la) ? la : Number.POSITIVE_INFINITY;
      const vb = Number.isFinite(lb) ? lb : Number.POSITIVE_INFINITY;
      if (va !== vb) return va - vb;
      return a.index - b.index;
    });
  const labelByRef = new Map();
  ordered.forEach(({ item }, i) => {
    labelByRef.set(item, i + 1);
  });
  return (items || []).map((item) => ({ ...item, label: labelByRef.get(item) }));
}

/** Default 2-column grid placement for a legacy slot image lacking geometry. */
function defaultGridPosition(index, total) {
  const cols = 2;
  const rows = Math.max(1, Math.ceil(Math.max(1, total) / cols));
  const col = index % cols;
  const row = Math.floor(index / cols);
  const cellW = 100 / cols;
  const cellH = 100 / rows;
  const pad = 2;
  return {
    x_pct: col * cellW + pad,
    y_pct: row * cellH + pad,
    w_pct: cellW - pad * 2,
    h_pct: cellH - pad * 2,
  };
}

/** Convert a legacy flat image_slots map into positioned canvas items. */
export function migrateImageSlotsToCanvas(imageSlots) {
  if (!imageSlots || typeof imageSlots !== "object") return [];
  const entries = Object.entries(imageSlots)
    .filter(([, dataUrl]) => Boolean(dataUrl))
    .map(([slot, dataUrl]) => ({ slot: Number(slot), dataUrl }))
    .sort((a, b) => a.slot - b.slot);

  return entries.map((entry, index) => {
    const pos = defaultGridPosition(index, entries.length);
    return clampCanvasItem({
      id: genCanvasId(),
      image_id: null,
      image_data_url: entry.dataUrl,
      ...pos,
      z_index: index + 1,
      label: index + 1,
    });
  });
}

/**
 * Normalize canvas items from a page form: prefer picture_canvas,
 * otherwise migrate legacy image_slots.
 */
export function normalizeCanvasItems(pageForm) {
  const list = Array.isArray(pageForm?.picture_canvas)
    ? pageForm.picture_canvas
    : null;
  if (list) {
    return list
      .filter((item) => item?.image_data_url)
      .map((item, index) =>
        clampCanvasItem({
          id:
            item.id ||
            (item.image_id != null ? String(item.image_id) : `canvas-${index}`),
          image_id: item.image_id ?? null,
          image_data_url: item.image_data_url,
          x_pct: item.x_pct,
          y_pct: item.y_pct,
          w_pct: item.w_pct,
          h_pct: item.h_pct,
          z_index: Number.isFinite(Number(item.z_index))
            ? Number(item.z_index)
            : index + 1,
          label: Number.isFinite(Number(item.label)) ? Number(item.label) : index + 1,
          row_index:
            item.row_index != null || item.block_index != null
              ? Number(item.row_index ?? item.block_index)
              : undefined,
          cell_row_span:
            Number.isFinite(Number(item.cell_row_span)) && Number(item.cell_row_span) > 0
              ? Number(item.cell_row_span)
              : 1,
        })
      );
  }
  return migrateImageSlotsToCanvas(pageForm?.image_slots);
}

export const PICTURE_CANVAS_LIMITS = {
  MIN_W_PCT,
  MIN_H_PCT,
  DEFAULT_W_PCT,
  DEFAULT_H_PCT,
};
