/**
 * Export service: SVG composition, validation, and browser download trigger.
 *
 * Pure utility functions that operate on canvas state to produce export-ready
 * SVG content. No React dependencies — consumed by ExportView (T04).
 */

import type {
  CanvasObject,
  ArtboardConfig,
  RectObject,
  CircleObject,
  EllipseObject,
  LineObject,
  ImageObject,
} from '../types/canvas';
import { toPx } from './artboardShapes';

// -- Validation ---------------------------------------------------------------

export type ValidationSeverity = 'error' | 'warning';

export interface ValidationIssue {
  severity: ValidationSeverity;
  message: string;
  objectId?: string;
  objectName?: string;
}

export interface ValidationResult {
  valid: boolean;
  issues: ValidationIssue[];
}

/**
 * Validate canvas objects for export readiness.
 *
 * Blocking errors (valid=false):
 * - TextObject present (must be converted to paths first)
 * - Missing artboard (no artboard configured)
 *
 * Warnings (valid=true but with issues):
 * - Raster-only images (non-SVG image sources)
 */
export function validateForExport(
  objects: CanvasObject[],
  artboard: ArtboardConfig | null,
): ValidationResult {
  const issues: ValidationIssue[] = [];

  // Check for missing artboard
  if (!artboard) {
    issues.push({
      severity: 'error',
      message: 'No artboard configured. Set up an artboard before exporting.',
    });
  }

  for (const obj of objects) {
    if (!obj.visible) continue;

    if (obj.type === 'text') {
      issues.push({
        severity: 'error',
        message: `Text object "${obj.name}" must be converted to paths before export.`,
        objectId: obj.id,
        objectName: obj.name,
      });
    }

    if (obj.type === 'image') {
      // SVG blob URLs contain 'image/svg+xml' in their origin or are data:image/svg+xml
      const isSvgSrc =
        obj.src.includes('image/svg+xml') || obj.src.endsWith('.svg');
      if (!isSvgSrc) {
        issues.push({
          severity: 'warning',
          message: `Image "${obj.name}" is raster-only and will be skipped in vector export.`,
          objectId: obj.id,
          objectName: obj.name,
        });
      }
    }
  }

  const hasErrors = issues.some((i) => i.severity === 'error');
  return { valid: !hasErrors, issues };
}

// -- SVG Composition ----------------------------------------------------------

/** Escape XML special characters in attribute values. */
function escapeXml(str: string): string {
  return str
    .replace(/&/g, '&amp;')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')
    .replace(/"/g, '&quot;')
    .replace(/'/g, '&apos;');
}

/** Format a number to a reasonable precision for SVG attributes. */
function n(value: number): string {
  return Number(value.toFixed(4)).toString();
}

function renderRect(obj: RectObject): string {
  const attrs = [
    `x="${n(obj.x)}"`,
    `y="${n(obj.y)}"`,
    `width="${n(obj.width)}"`,
    `height="${n(obj.height)}"`,
    `fill="${escapeXml(obj.fill)}"`,
    `stroke="${escapeXml(obj.stroke)}"`,
    `stroke-width="${n(obj.strokeWidth)}"`,
    `opacity="${n(obj.opacity)}"`,
  ];
  if (obj.rotation !== 0) {
    const cx = obj.x + obj.width / 2;
    const cy = obj.y + obj.height / 2;
    attrs.push(`transform="rotate(${n(obj.rotation)} ${n(cx)} ${n(cy)})"`);
  }
  return `  <rect ${attrs.join(' ')} />`;
}

function renderCircle(obj: CircleObject): string {
  const attrs = [
    `cx="${n(obj.x)}"`,
    `cy="${n(obj.y)}"`,
    `r="${n(obj.radius)}"`,
    `fill="${escapeXml(obj.fill)}"`,
    `stroke="${escapeXml(obj.stroke)}"`,
    `stroke-width="${n(obj.strokeWidth)}"`,
    `opacity="${n(obj.opacity)}"`,
  ];
  if (obj.rotation !== 0) {
    attrs.push(`transform="rotate(${n(obj.rotation)} ${n(obj.x)} ${n(obj.y)})"`);
  }
  return `  <circle ${attrs.join(' ')} />`;
}

function renderEllipse(obj: EllipseObject): string {
  const attrs = [
    `cx="${n(obj.x)}"`,
    `cy="${n(obj.y)}"`,
    `rx="${n(obj.radiusX)}"`,
    `ry="${n(obj.radiusY)}"`,
    `fill="${escapeXml(obj.fill)}"`,
    `stroke="${escapeXml(obj.stroke)}"`,
    `stroke-width="${n(obj.strokeWidth)}"`,
    `opacity="${n(obj.opacity)}"`,
  ];
  if (obj.rotation !== 0) {
    attrs.push(`transform="rotate(${n(obj.rotation)} ${n(obj.x)} ${n(obj.y)})"`);
  }
  return `  <ellipse ${attrs.join(' ')} />`;
}

function renderLine(obj: LineObject): string {
  // LineObject.points are [x1, y1, x2, y2, ...] relative to obj.x, obj.y
  const pointPairs: string[] = [];
  for (let i = 0; i < obj.points.length; i += 2) {
    const px = obj.x + (obj.points[i] ?? 0);
    const py = obj.y + (obj.points[i + 1] ?? 0);
    pointPairs.push(`${n(px)},${n(py)}`);
  }
  const attrs = [
    `points="${pointPairs.join(' ')}"`,
    `fill="none"`,
    `stroke="${escapeXml(obj.stroke)}"`,
    `stroke-width="${n(obj.strokeWidth)}"`,
    `opacity="${n(obj.opacity)}"`,
  ];
  if (obj.dash.length > 0) {
    attrs.push(`stroke-dasharray="${obj.dash.map(n).join(' ')}"`);
  }
  if (obj.rotation !== 0) {
    attrs.push(`transform="rotate(${n(obj.rotation)} ${n(obj.x)} ${n(obj.y)})"`);
  }
  return `  <polyline ${attrs.join(' ')} />`;
}

function renderImage(obj: ImageObject): string {
  // For SVG blob URLs, we can reference them. In a full implementation,
  // we'd inline the SVG content by fetching the blob URL.
  // For now, embed as <image> with href.
  const attrs = [
    `x="${n(obj.x)}"`,
    `y="${n(obj.y)}"`,
    `width="${n(obj.width)}"`,
    `height="${n(obj.height)}"`,
    `href="${escapeXml(obj.src)}"`,
    `opacity="${n(obj.opacity)}"`,
  ];
  if (obj.rotation !== 0) {
    const cx = obj.x + obj.width / 2;
    const cy = obj.y + obj.height / 2;
    attrs.push(`transform="rotate(${n(obj.rotation)} ${n(cx)} ${n(cy)})"`);
  }
  return `  <image ${attrs.join(' ')} />`;
}

/**
 * Render a single canvas object to an SVG element string.
 * Returns null for objects that cannot be rendered (text, hidden, raster images).
 */
function renderObject(obj: CanvasObject): string | null {
  if (!obj.visible) return null;

  switch (obj.type) {
    case 'rect':
      return renderRect(obj);
    case 'circle':
      return renderCircle(obj);
    case 'ellipse':
      return renderEllipse(obj);
    case 'line':
      return renderLine(obj);
    case 'image': {
      // Skip raster images — only SVG images are meaningful for vector export
      const isSvgSrc =
        obj.src.includes('image/svg+xml') || obj.src.endsWith('.svg');
      if (!isSvgSrc) return null;
      return renderImage(obj);
    }
    case 'text':
      // Text objects should have been converted to paths. Skip in SVG composition.
      return null;
    default:
      return null;
  }
}

/**
 * Compose visible canvas objects into an SVG string with correct coordinate
 * space and real-world unit dimensions.
 *
 * - viewBox matches artboard dimensions in pixels
 * - width/height attributes use real-world units (e.g. `width="4in"`)
 * - Objects with `visible: false` are skipped
 * - TextObject and raster ImageObject are skipped
 */
export function composeCanvasSVG(
  objects: CanvasObject[],
  artboard: ArtboardConfig,
): string {
  const pxW = toPx(artboard.width, artboard.unit);
  const pxH = toPx(artboard.height, artboard.unit);

  // Real-world unit suffix for width/height attributes
  const unitSuffix = artboard.unit === 'inches' ? 'in' : 'mm';
  const realW = `${n(artboard.width)}${unitSuffix}`;
  const realH = `${n(artboard.height)}${unitSuffix}`;

  const elements = objects
    .map(renderObject)
    .filter((el): el is string => el !== null);

  const lines = [
    `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 ${n(pxW)} ${n(pxH)}" width="${realW}" height="${realH}">`,
    ...elements,
    '</svg>',
  ];

  return lines.join('\n');
}

// -- Download Trigger ---------------------------------------------------------

/**
 * Trigger a browser file download from a Blob.
 *
 * Creates a hidden anchor element with a blob URL, clicks it to start the
 * download, then cleans up the DOM element and revokes the blob URL.
 */
export function triggerDownload(blob: Blob, filename: string): void {
  const url = URL.createObjectURL(blob);
  const a = document.createElement('a');
  a.href = url;
  a.download = filename;
  a.style.display = 'none';
  document.body.appendChild(a);
  a.click();
  // Clean up after a short delay to ensure the download starts
  setTimeout(() => {
    document.body.removeChild(a);
    URL.revokeObjectURL(url);
  }, 100);
}
