import type { PenNode } from '@/types/pen';
import {
  defaultLineHeight,
  estimateLineWidth,
  hasCjkText as _hasCjkText,
} from '@/canvas/canvas-text-measure';

// ---------------------------------------------------------------------------
// Pure utility functions extracted from design-generator.ts
// ---------------------------------------------------------------------------

export function clamp(value: number, min: number, max: number): number {
  return Math.min(max, Math.max(min, value));
}

export function toSizeNumber(value: number | string | undefined, fallback: number): number {
  if (typeof value === 'number' && Number.isFinite(value)) return value;
  if (typeof value === 'string') {
    const wrapped = value.match(/\((\d+(?:\.\d+)?)\)/);
    if (wrapped) return Number(wrapped[1]);
    const parsed = Number(value);
    if (Number.isFinite(parsed)) return parsed;
  }
  return fallback;
}

export function toGapNumber(value: number | string | undefined): number {
  if (typeof value === 'number') return value;
  if (typeof value === 'string') {
    const parsed = Number(value);
    if (Number.isFinite(parsed)) return parsed;
  }
  return 0;
}

export function toStrokeThicknessNumber(
  stroke: { thickness?: number | [number, number, number, number] } | undefined,
  fallback: number,
): number {
  if (!stroke) return fallback;
  const t = stroke.thickness;
  if (typeof t === 'number' && Number.isFinite(t)) return t;
  if (Array.isArray(t) && t.length > 0 && Number.isFinite(t[0])) return t[0];
  return fallback;
}

export function toCornerRadiusNumber(
  value: number | [number, number, number, number] | undefined,
  fallback: number,
): number {
  if (typeof value === 'number' && Number.isFinite(value)) return value;
  if (Array.isArray(value) && value.length > 0 && Number.isFinite(value[0])) return value[0];
  return fallback;
}

export function parsePaddingValues(
  padding: number | [number, number] | [number, number, number, number] | string | undefined,
): { top: number; right: number; bottom: number; left: number } {
  if (typeof padding === 'number') {
    return { top: padding, right: padding, bottom: padding, left: padding };
  }
  if (typeof padding === 'string') {
    const parsed = Number(padding);
    if (Number.isFinite(parsed)) {
      return { top: parsed, right: parsed, bottom: parsed, left: parsed };
    }
    return { top: 0, right: 0, bottom: 0, left: 0 };
  }
  if (Array.isArray(padding) && padding.length === 2) {
    return {
      top: padding[0],
      right: padding[1],
      bottom: padding[0],
      left: padding[1],
    };
  }
  if (Array.isArray(padding) && padding.length === 4) {
    return {
      top: padding[0],
      right: padding[1],
      bottom: padding[2],
      left: padding[3],
    };
  }
  return { top: 0, right: 0, bottom: 0, left: 0 };
}

// ---------------------------------------------------------------------------
// Text measurement utilities
// ---------------------------------------------------------------------------

/**
 * Estimate auto-height for text with wrapping.
 * `canvasWidth` is used as fallback when parentContentWidth is unavailable.
 */
export function estimateAutoHeight(
  text: string,
  fontSize: number,
  lineHeight: number,
  parentContentWidth?: number,
  canvasWidth = 1200,
): number {
  const hasCjk = _hasCjkText(text);
  const minLh = hasCjk ? (fontSize >= 28 ? 1.28 : 1.5) : fontSize >= 28 ? 1.15 : 1.35;
  const effectiveLh = Math.max(lineHeight, minLh);

  const availW =
    parentContentWidth && parentContentWidth > 0
      ? Math.max(120, parentContentWidth)
      : canvasWidth <= 520
        ? Math.max(220, canvasWidth * 0.74)
        : Math.max(260, canvasWidth * 0.5);

  const logicalLines = text.split(/\r?\n/);
  const wrappedLineCount = logicalLines.reduce((sum, line) => {
    // Use the canonical estimateLineWidth from canvas-text-measure with
    // a safety factor to avoid underestimating wrapping.
    const safetyW = _hasCjkText(line) ? 1.06 : 1.14;
    const lineWidth = estimateLineWidth(line, fontSize) * safetyW;
    return sum + Math.max(1, Math.ceil(lineWidth / availW));
  }, 0);
  const safetyMargin = fontSize >= 28 ? 1.15 : 1.1;
  return Math.round(Math.max(1, wrappedLineCount) * fontSize * effectiveLh * safetyMargin);
}

export function estimateNodeIntrinsicHeight(
  node: PenNode,
  parentContentWidth?: number,
  canvasWidth = 1200,
): number {
  const explicitHeight = toSizeNumber(
    ('height' in node ? node.height : undefined) as number | string | undefined,
    0,
  );

  let textHeight = 0;
  if (node.type === 'text') {
    const fs = node.fontSize ?? 16;
    const lh = node.lineHeight ?? defaultLineHeight(fs);
    if (
      typeof node.content === 'string' &&
      node.content.trim() &&
      node.textGrowth === 'fixed-width' &&
      parentContentWidth &&
      parentContentWidth > 0
    ) {
      textHeight = estimateAutoHeight(node.content.trim(), fs, lh, parentContentWidth, canvasWidth);
    } else {
      textHeight = Math.max(20, Math.round(fs * lh));
    }
  }

  if (!('children' in node) || !Array.isArray(node.children) || node.children.length === 0) {
    if (node.type === 'text' && textHeight > 0) {
      return Math.max(explicitHeight, textHeight);
    }
    return explicitHeight || textHeight || 80;
  }

  const padding = parsePaddingValues('padding' in node ? node.padding : undefined);
  const gap = toGapNumber('gap' in node ? node.gap : undefined);
  const layout = 'layout' in node ? node.layout : undefined;
  const children = node.children;

  const nodeW = toSizeNumber(
    ('width' in node ? node.width : undefined) as number | string | undefined,
    0,
  );
  const childContentW = nodeW > 0 ? nodeW - padding.left - padding.right : 0;

  if (layout === 'vertical') {
    let total = padding.top + padding.bottom;
    for (const child of children) {
      total += estimateNodeIntrinsicHeight(child, childContentW || undefined, canvasWidth);
    }
    if (children.length > 1) {
      total += gap * (children.length - 1);
    }
    return Math.max(explicitHeight, total);
  }

  if (layout === 'horizontal') {
    const childCount = children.length;
    const totalGap = childCount > 1 ? gap * (childCount - 1) : 0;
    const perChildW =
      childContentW > 0 && childCount > 0 ? (childContentW - totalGap) / childCount : 0;
    let maxChild = 0;
    for (const child of children) {
      const childW = toSizeNumber(
        ('width' in child ? child.width : undefined) as number | string | undefined,
        0,
      );
      const effectiveW = childW > 0 ? childW : perChildW > 0 ? perChildW : undefined;
      maxChild = Math.max(maxChild, estimateNodeIntrinsicHeight(child, effectiveW, canvasWidth));
    }
    const total = padding.top + padding.bottom + maxChild;
    return Math.max(explicitHeight, total);
  }

  let boundsBottom = 0;
  for (const child of children) {
    const childY = typeof child.y === 'number' ? child.y : 0;
    const childBottom =
      childY + estimateNodeIntrinsicHeight(child, childContentW || undefined, canvasWidth);
    boundsBottom = Math.max(boundsBottom, childBottom);
  }

  const contentHeight = boundsBottom + padding.bottom;
  return Math.max(explicitHeight, contentHeight);
}

// ---------------------------------------------------------------------------
// Color & fill utilities
// ---------------------------------------------------------------------------

export function extractPrimaryColor(fill: unknown): string | null {
  if (!Array.isArray(fill) || fill.length === 0) return null;
  const first = fill[0];
  if (!first || typeof first !== 'object') return null;
  const solid = first as { type?: string; color?: string };
  if (solid.type !== 'solid') return null;
  return solid.color ?? null;
}

export function isFillDark(fill: unknown): boolean {
  if (!Array.isArray(fill) || fill.length === 0) return true;
  const first = fill[0] as { type?: string; color?: string };
  if (first.type !== 'solid' || !first.color) return true;
  const c = first.color;
  if (c.startsWith('rgba')) {
    const parts = c
      .replace(/rgba?\(|\)/g, '')
      .split(',')
      .map(Number);
    if (parts.length >= 3) return (parts[0] * 299 + parts[1] * 587 + parts[2] * 114) / 1000 < 128;
  }
  const hex = c.replace('#', '');
  if (hex.length < 6) return true;
  const r = parseInt(hex.substring(0, 2), 16);
  const g = parseInt(hex.substring(2, 4), 16);
  const b = parseInt(hex.substring(4, 6), 16);
  return (r * 299 + g * 587 + b * 114) / 1000 < 128;
}

export function getPlaceholderColors(fill: unknown): {
  fillColor: string;
  strokeColor: string;
  textColor: string;
} {
  if (isFillDark(fill)) {
    return {
      fillColor: '#111627',
      strokeColor: '#1E2440',
      textColor: '#2A3050',
    };
  }
  return { fillColor: '#F1F5F9', strokeColor: '#D1D5DB', textColor: '#C0C4CC' };
}

// ---------------------------------------------------------------------------
// Text helpers
// ---------------------------------------------------------------------------

export function getTextContentForNode(node: PenNode): string {
  if (node.type !== 'text') return '';
  return typeof node.content === 'string'
    ? node.content
    : Array.isArray(node.content)
      ? node.content.map((s: { text: string }) => s.text).join('')
      : '';
}

// Re-export canonical hasCjkText from canvas-text-measure so existing
// importers (role-resolver, typography roles) continue to work.
export const hasCjkText = _hasCjkText;

// ---------------------------------------------------------------------------
// Phone placeholder SVG
// ---------------------------------------------------------------------------

export function createPhonePlaceholderDataUri(width: number, height: number, dark = true): string {
  const w = Math.max(140, Math.round(width));
  const h = Math.max(240, Math.round(height));
  const pad = Math.max(8, Math.round(Math.min(w, h) * 0.05));
  const innerW = w - pad * 2;
  const innerH = h - pad * 2;
  const outerR = Math.round(Math.min(w, h) * 0.12);
  const innerR = Math.max(outerR - pad, 8);

  const bgColor = dark ? '#111627' : '#F1F5F9';
  const strokeColor = dark ? '#1E2440' : '#D1D5DB';

  const svg =
    `<svg xmlns="http://www.w3.org/2000/svg" width="${w}" height="${h}" viewBox="0 0 ${w} ${h}">` +
    `<rect width="${w}" height="${h}" rx="${outerR}" fill="${bgColor}"/>` +
    `<rect x="${pad}" y="${pad}" width="${innerW}" height="${innerH}" rx="${innerR}" fill="none" stroke="${strokeColor}" stroke-width="1"/>` +
    `</svg>`;
  return `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
}
