export interface AsukaFixedSlotConstraintsLike {
  lockX?: boolean;
  lockY?: boolean;
  lockRotation?: boolean;
  minWidth?: number;
  minHeight?: number;
  maxWidth?: number;
  maxHeight?: number;
  allowedColors?: string[];
  minChars?: number;
  maxChars?: number;
  maxLines?: number;
  allowedFonts?: string[];
}

const finiteNumber = (value: unknown): number | undefined => {
  const parsed = Number(value);
  return Number.isFinite(parsed) ? parsed : undefined;
};

const normalizedList = (value: unknown): string[] | undefined => {
  if (!Array.isArray(value)) return undefined;
  const items = value.map((item) => String(item ?? '').trim()).filter(Boolean);
  return items.length ? items : undefined;
};

export const normalizeFixedSlotConstraints = (input?: AsukaFixedSlotConstraintsLike | null): AsukaFixedSlotConstraintsLike => {
  const source = input && typeof input === 'object' ? input : {};
  const positive = (value: unknown) => {
    const parsed = finiteNumber(value);
    return typeof parsed === 'number' && parsed > 0 ? parsed : undefined;
  };
  const nonNegativeInteger = (value: unknown) => {
    const parsed = finiteNumber(value);
    return typeof parsed === 'number' && parsed >= 0 ? Math.floor(parsed) : undefined;
  };
  const positiveInteger = (value: unknown) => {
    const parsed = finiteNumber(value);
    return typeof parsed === 'number' && parsed >= 1 ? Math.floor(parsed) : undefined;
  };

  return {
    lockX: source.lockX === true,
    lockY: source.lockY === true,
    lockRotation: source.lockRotation === true,
    minWidth: positive(source.minWidth),
    minHeight: positive(source.minHeight),
    maxWidth: positive(source.maxWidth),
    maxHeight: positive(source.maxHeight),
    allowedColors: normalizedList(source.allowedColors),
    minChars: nonNegativeInteger(source.minChars),
    maxChars: nonNegativeInteger(source.maxChars),
    maxLines: positiveInteger(source.maxLines),
    allowedFonts: normalizedList(source.allowedFonts),
  };
};

export const getObjectFixedSlotConstraints = (object: any): AsukaFixedSlotConstraintsLike => {
  const metadata = object?.metadata && typeof object.metadata === 'object' ? object.metadata : {};
  return normalizeFixedSlotConstraints(
    metadata.fixedSlotConstraints
      ?? metadata.fixedSlot?.constraints
      ?? object?._asukaFixedSlotConstraints
      ?? object?._asukaFixedConstraints
      ?? null,
  );
};

export const constrainFixedSlotTransformPatch = <T extends Record<string, unknown>>(
  patch: T,
  constraints?: AsukaFixedSlotConstraintsLike | null,
): T => {
  const next = { ...(patch ?? {}) } as Record<string, unknown>;
  const normalized = normalizeFixedSlotConstraints(constraints);

  if (normalized.lockX) {
    for (const key of ['x', 'deltaX', 'moveX', 'nudgeX']) delete next[key];
  }
  if (normalized.lockY) {
    for (const key of ['y', 'deltaY', 'moveY', 'nudgeY']) delete next[key];
  }
  if (normalized.lockRotation) {
    for (const key of ['rotation', 'rotationBy', 'rotationZ', 'angle']) delete next[key];
  }

  return next as T;
};

export const constrainFixedSlotTextInput = <T extends {
  text?: string;
  color?: string;
  fontFamily?: string;
}>(input: T, constraints?: AsukaFixedSlotConstraintsLike | null): T => {
  const normalized = normalizeFixedSlotConstraints(constraints);
  const next = { ...(input ?? {}) } as T;
  let text = String(next.text ?? '');
  if (typeof normalized.maxChars === 'number') text = text.slice(0, normalized.maxChars);
  if (typeof normalized.maxLines === 'number') text = text.split(/\r?\n/).slice(0, normalized.maxLines).join('\n');
  next.text = text;

  const allowedFonts = normalized.allowedFonts ?? [];
  const currentFont = String(next.fontFamily ?? '').trim();
  if (allowedFonts.length && !allowedFonts.some((font) => font.toLowerCase() === currentFont.toLowerCase())) {
    next.fontFamily = allowedFonts[0];
  }

  const allowedColors = normalized.allowedColors ?? [];
  const currentColor = String(next.color ?? '').trim();
  if (allowedColors.length && !allowedColors.some((color) => color.toLowerCase() === currentColor.toLowerCase())) {
    next.color = allowedColors[0];
  }

  return next;
};
