import { getFontFamily } from "@/pages/editor/utils/fontFamily";

/** CSS font-weight values for canvas/Konva text rendering. */
export const getCssFontWeight = (fontWeight?: string): number => {
  if (!fontWeight || fontWeight === "normal") return 400;
  if (fontWeight === "bold" || fontWeight === "bolder") return 700;

  const n = parseInt(fontWeight, 10);
  if (!Number.isNaN(n)) return n;

  return 400;
};

/** Konva.Text uses `fontStyle: bold` (canvas keyword), not numeric weight. */
export const usesKonvaBoldStyle = (fontWeight?: string): boolean => {
  if (!fontWeight || fontWeight === "normal") return false;
  if (fontWeight === "bold" || fontWeight === "bolder") return true;
  const n = parseInt(fontWeight, 10);
  return !Number.isNaN(n) && n >= 600;
};

/** Canvas/CSS `font` string shared by editor measurement and viewer layout. */
export const buildCanvasFont = (
  fontSize: number,
  fontFamily?: string,
  fontWeight?: string,
): string => {
  const weight = getCssFontWeight(fontWeight);
  const family = getFontFamily(fontFamily);
  return `${weight} ${fontSize}px ${family}`;
};

/** Canvas font string that matches Konva.Text (`fontStyle: bold`). */
export const buildKonvaCanvasFont = (
  fontSize: number,
  fontFamily?: string,
  fontWeight?: string,
): string => {
  const family = getFontFamily(fontFamily);
  const style = usesKonvaBoldStyle(fontWeight) ? "bold" : "normal";
  return `${style} ${fontSize}px ${family}`;
};

const LINE_BREAK_RE = /[\r\n\u2028\u2029]/;

export const isSingleLineText = (text?: string): boolean =>
  !text || !LINE_BREAK_RE.test(text);

/**
 * Konva can wrap text inside a fixed width without inserting `\n`.
 * Use stored height to detect multi-line layout.
 */
export const usesWrappedLayout = (
  text: string | undefined,
  height: number | undefined,
  fontSize: number,
  lineHeight = 1.2,
): boolean => {
  if (!isSingleLineText(text)) return true;
  const linePx = fontSize * lineHeight;
  return (height ?? 0) > linePx * 1.35;
};

/** Extra px so HTML layout does not wrap tighter than Konva/canvas metrics. */
export const SINGLE_LINE_WIDTH_SLACK_PX = 12;
export const DEFAULT_TEXT_MAX_WIDTH_PX = 520;
const MIN_TEXT_MAX_WIDTH_PX = 80;

/**
 * Maximum width for text boxes.
 * - never larger than page/view bounds
 * - has a sane default for long single-line text
 */
export const resolveTextMaxWidth = (args: {
  anchorX: number;
  pageWidth?: number;
  defaultMaxWidth?: number;
  edgePadding?: number;
}): number => {
  const {
    anchorX,
    pageWidth,
    defaultMaxWidth = DEFAULT_TEXT_MAX_WIDTH_PX,
    edgePadding = 8,
  } = args;

  const fromAnchor = Math.floor(anchorX - edgePadding);
  const fromPage =
    pageWidth !== undefined
      ? Math.floor(Math.min(anchorX - edgePadding, pageWidth - edgePadding))
      : fromAnchor;

  const hardLimit = Math.max(MIN_TEXT_MAX_WIDTH_PX, fromPage);
  return Math.max(
    MIN_TEXT_MAX_WIDTH_PX,
    Math.min(defaultMaxWidth, hardLimit),
  );
};

export type TextMeasureOptions = {
  fontSize: number;
  fontFamily?: string;
  fontWeight?: string;
  letterSpacing?: number;
  lineHeight?: number;
};

const measureLineWidth = (
  ctx: CanvasRenderingContext2D,
  line: string,
  letterSpacing: number,
): number => {
  let lineWidth = ctx.measureText(line).width;
  if (letterSpacing && line.length > 1) {
    lineWidth += letterSpacing * (line.length - 1);
  }
  return lineWidth;
};

/** Measure text using CSS and Konva font strings; returns the wider result. */
export const measureTextBlock = (
  text: string,
  options: TextMeasureOptions,
): { width: number; height: number; lineCount: number } => {
  if (!text) return { width: 0, height: 0, lineCount: 0 };

  const canvas = document.createElement("canvas");
  const ctx = canvas.getContext("2d");
  if (!ctx) return { width: 0, height: 0, lineCount: 1 };

  const {
    fontSize,
    fontFamily,
    fontWeight,
    letterSpacing = 0,
    lineHeight = 1.2,
  } = options;

  const lines = text.split(LINE_BREAK_RE);
  const fonts = [
    buildCanvasFont(fontSize, fontFamily, fontWeight),
    buildKonvaCanvasFont(fontSize, fontFamily, fontWeight),
  ];

  let maxWidth = 0;
  for (const font of fonts) {
    ctx.font = font;
    for (const line of lines) {
      maxWidth = Math.max(maxWidth, measureLineWidth(ctx, line, letterSpacing));
    }
  }

  return {
    width: Math.ceil(maxWidth),
    height: Math.ceil(lines.length * fontSize * lineHeight),
    lineCount: lines.length,
  };
};

/** Width used for layout: at least stored Konva width and canvas measurement. */
export const getEffectiveTextWidth = (
  storedWidth: number | undefined,
  text: string | undefined,
  options: TextMeasureOptions,
  singleLine: boolean,
): number | undefined => {
  if (!text && !storedWidth) return undefined;

  const measured = measureTextBlock(text || "", options).width;
  const base = Math.max(storedWidth || 0, measured);

  if (!singleLine) return base || undefined;
  return Math.max(1, base + SINGLE_LINE_WIDTH_SLACK_PX);
};

/** Scaled width for viewer text boxes (includes slack to avoid tighter CSS wrapping than Konva). */
export const getScaledTextWidth = (
  width: number | undefined,
  scale: number,
): number | undefined => {
  if (!width) return undefined;
  return Math.ceil(width * scale) + Math.ceil(SINGLE_LINE_WIDTH_SLACK_PX * scale);
};

export type TextAlignValue = "left" | "center" | "right";

export type ViewerTextLayout = {
  leftPx: number;
  widthPx: number | undefined;
  textAlign: TextAlignValue;
  transform: string | undefined;
  transformOrigin: "top right";
};

/**
 * Viewer HTML layout matching Konva TextShape: (x, y) is the top-right anchor of the text box.
 * Text alignment is applied inside a fixed-width box that extends left from the anchor.
 */
export function getViewerTextLayout(args: {
  x: number;
  width?: number;
  text?: string;
  textAlign?: TextAlignValue;
  rotation?: number;
  scale: number;
  measureOpts: TextMeasureOptions;
  wrapped: boolean;
}): ViewerTextLayout {
  const {
    x,
    width: storedWidth,
    text,
    textAlign = "right",
    rotation = 0,
    scale,
    measureOpts,
    wrapped,
  } = args;

  const layoutWidth = getEffectiveTextWidth(
    storedWidth,
    text,
    measureOpts,
    !wrapped,
  );
  const widthPx = getScaledTextWidth(layoutWidth, scale);
  const slackPx =
    widthPx !== undefined ? Math.ceil(SINGLE_LINE_WIDTH_SLACK_PX * scale) : 0;
  const rightPx = x * scale;
  const boxWidthUnscaled = layoutWidth ?? storedWidth ?? 0;

  let leftPx: number;
  if (widthPx !== undefined && boxWidthUnscaled > 0) {
    leftPx = Math.round(rightPx - boxWidthUnscaled * scale) - slackPx;
  } else {
    leftPx = Math.round(rightPx);
  }

  const rotationTransform = rotation ? `rotate(${rotation}deg)` : undefined;

  return {
    leftPx,
    widthPx,
    textAlign,
    transform: rotationTransform,
    transformOrigin: "top right",
  };
}
