using DocumentOperator.Application.AddAnnotation;
using DocumentOperator.Application.AddStamp;
using DocumentOperator.Application.MergePdfs;
using DocumentOperator.Domain.Common.Exceptions;
using DocumentOperator.Domain.Models.ValueObjects;
using MediatR;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;

namespace DocumentOperator.API.Controllers;

/// <summary>
/// Controller for PDF operations (merge, stamp, annotate).
/// </summary>
[ApiController]
[Route("api/pdf/operations")]
public class PdfOperationsController(IMediator mediator) : ControllerBase
{
    /// <summary>
    /// Merges multiple PDF files into a single PDF.
    /// Supports multipart/form-data file upload.
    /// </summary>
    /// <param name="files">PDF files to merge (minimum 2 required)</param>
    /// <param name="cancellationToken">Cancellation token</param>
    /// <returns>Merged PDF file</returns>
    /// <response code="200">PDFs merged successfully - returns merged PDF</response>
    /// <response code="400">Invalid input (fewer than 2 files, corrupted PDF)</response>
    /// <response code="500">Internal server error during PDF processing</response>
    [HttpPost("merge", Name = "MergeFromFiles")]
    [Consumes("multipart/form-data")]
    [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
    public async Task<IActionResult> MergeFromFiles(
        [FromForm] List<IFormFile> files,
        CancellationToken cancellationToken)
    {
        // Convert IFormFile[] to Stream[] (use OpenReadStream directly - no buffering)
        var streams = files.Select(f => f.OpenReadStream()).ToList();

        // Send command to MediatR (no page ranges for now - multipart binding is complex)
        var command = new MergePdfsCommand 
        { 
            PdfStreams = streams,
            PageRanges = null
        };
        
        byte[] mergedPdf = await mediator.Send(command, cancellationToken);

        // Return merged PDF
        return File(mergedPdf, "application/pdf", "merged.pdf");
    }

    /// <summary>
    /// Merges multiple PDF files into a single PDF.
    /// Supports Base64-encoded PDFs via JSON payload.
    /// </summary>
    /// <param name="request">Request containing Base64-encoded PDFs and optional page ranges</param>
    /// <param name="cancellationToken">Cancellation token</param>
    /// <returns>Merged PDF file</returns>
    /// <response code="200">PDFs merged successfully - returns merged PDF</response>
    /// <response code="400">Invalid input (Base64 format error, fewer than 2 files, corrupted PDF, invalid page range)</response>
    /// <response code="500">Internal server error during PDF processing</response>
    [HttpPost("merge", Name = "MergeFromBase64")]
    [Consumes("application/json")]
    [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
    public async Task<IActionResult> MergeFromBase64(
        [FromBody] MergePdfsBase64Request request,
        CancellationToken cancellationToken)
    {
        // Convert Base64[] to MemoryStream[]
        List<Stream> streams = [];
        try
        {
            foreach (var base64Pdf in request.Base64Pdfs)
            {
                byte[] pdfBytes = Convert.FromBase64String(base64Pdf);
                streams.Add(new MemoryStream(pdfBytes));
            }
        }
        catch (FormatException ex)
        {
            // Dispose opened streams on error
            foreach (var stream in streams) stream.Dispose();
            throw new BadRequestException("Invalid Base64 format: " + ex.Message);
        }

        var command = new MergePdfsCommand 
        { 
            PdfStreams = streams,
            PageRanges = request.PageRanges
        };
        
        byte[] mergedPdf = await mediator.Send(command, cancellationToken);

        // Cleanup streams (important for MemoryStreams we created)
        foreach (var stream in streams) stream.Dispose();

        return File(mergedPdf, "application/pdf", "merged.pdf");
    }

    /// <summary>
    /// Adds an annotation to a PDF document.
    /// Supports multipart/form-data file upload.
    /// </summary>
    /// <param name="request">Multipart form data containing PDF file and annotation parameters</param>
    /// <param name="cancellationToken">Cancellation token</param>
    /// <returns>Annotated PDF file</returns>
    /// <response code="200">Annotation added successfully - returns annotated PDF</response>
    /// <response code="400">Invalid input (invalid page number, missing required parameters)</response>
    /// <response code="500">Internal server error during PDF processing</response>
    [HttpPost("annotate", Name = "AnnotateFromFile")]
    [Consumes("multipart/form-data")]
    [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
    public async Task<IActionResult> AnnotateFromFile(
        [FromForm] AddAnnotationMultipartRequest request,
        CancellationToken cancellationToken)
    {
        // Calculate X2, Y2 from Width/Height if provided
        double x2 = request.X2 ?? request.X1 + (request.Width ?? throw new BadRequestException("Either X2 or Width must be provided"));
        double y2 = request.Y2 ?? request.Y1 + (request.Height ?? throw new BadRequestException("Either Y2 or Height must be provided"));

        var command = new AddAnnotationCommand
        {
            PdfStream = request.File.OpenReadStream(),
            AnnotationType = request.AnnotationType,
            PageNumber = request.PageNumber,
            Rectangle = (request.X1, request.Y1, x2, y2),
            Content = request.Content,
            Author = request.Author,
            Color = request.Color,
            TextMarkupStyle = request.TextMarkupStyle,
            Origin = request.Origin
        };

        byte[] annotatedPdf = await mediator.Send(command, cancellationToken);

        return File(annotatedPdf, "application/pdf", "annotated.pdf");
    }

    /// <summary>
    /// Adds an annotation to a PDF document.
    /// Supports Base64-encoded PDF via JSON payload.
    /// </summary>
    /// <param name="command">Command containing all annotation parameters (including Base64 PDF)</param>
    /// <param name="cancellationToken">Cancellation token</param>
    /// <returns>Annotated PDF file</returns>
    /// <response code="200">Annotation added successfully - returns annotated PDF</response>
    /// <response code="400">Invalid input (Base64 format error, invalid page number, missing required parameters)</response>
    /// <response code="500">Internal server error during PDF processing</response>
    [HttpPost("annotate", Name = "AnnotateFromBase64")]
    [Consumes("application/json")]
    [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
    public async Task<IActionResult> AnnotateFromBase64(
        [FromBody] AddAnnotationBase64Command command,
        CancellationToken cancellationToken)
    {
        // Convert Base64 to MemoryStream
        Stream pdfStream;
        try
        {
            byte[] pdfBytes = Convert.FromBase64String(command.Base64Pdf);
            pdfStream = new MemoryStream(pdfBytes);
        }
        catch (FormatException ex)
        {
            throw new BadRequestException("Invalid Base64 format: " + ex.Message);
        }

        // Calculate X2, Y2 from Width/Height if provided
        double x2 = command.X2 ?? command.X1 + (command.Width ?? throw new BadRequestException("Either X2 or Width must be provided"));
        double y2 = command.Y2 ?? command.Y1 + (command.Height ?? throw new BadRequestException("Either Y2 or Height must be provided"));

        var annotationCommand = new AddAnnotationCommand
        {
            PdfStream = pdfStream,
            AnnotationType = command.AnnotationType,
            PageNumber = command.PageNumber,
            Rectangle = (command.X1, command.Y1, x2, y2),
            Content = command.Content,
            Author = command.Author,
            Color = command.Color,
            TextMarkupStyle = command.TextMarkupStyle,
            Origin = command.Origin
        };

        byte[] annotatedPdf = await mediator.Send(annotationCommand, cancellationToken);

        // Cleanup stream
        pdfStream.Dispose();

        return File(annotatedPdf, "application/pdf", "annotated.pdf");
    }

    /// <summary>
    /// Adds a stamp (text, image, or predefined) to PDF pages.
    /// Supports multipart/form-data file upload.
    /// </summary>
    /// <param name="request">Multipart form data containing PDF file and stamp parameters</param>
    /// <param name="cancellationToken">Cancellation token</param>
    /// <returns>Stamped PDF file</returns>
    /// <response code="200">Stamp added successfully - returns stamped PDF</response>
    /// <response code="400">Invalid input (invalid page number, missing required parameters, invalid image format)</response>
    /// <response code="500">Internal server error during PDF processing</response>
    [HttpPost("stamp", Name = "AddStampFromFile")]
    [Consumes("multipart/form-data")]
    [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
    public async Task<IActionResult> AddStampFromFile(
        [FromForm] AddStampMultipartRequest request,
        CancellationToken cancellationToken)
    {
        // Convert ImageFile to byte[] if provided
        byte[]? imageBytes = null;
        if (request.ImageFile != null)
        {
            using var ms = new MemoryStream();
            await request.ImageFile.CopyToAsync(ms, cancellationToken);
            imageBytes = ms.ToArray();
        }

        var command = new AddStampCommand
        {
            PdfStream = request.File.OpenReadStream(),
            StampType = request.StampType,
            PageNumbers = request.PageNumbers,
            Position = (request.X, request.Y),
            Size = request.Width.HasValue && request.Height.HasValue 
                ? (request.Width.Value, request.Height.Value) 
                : null,
            Origin = request.Origin,
            Text = request.Text,
            FontName = request.FontName,
            FontSize = request.FontSize,
            Color = request.Color,
            Opacity = request.Opacity,
            Rotation = request.Rotation,
            Placement = request.Placement,
            ImageBytes = imageBytes,
            PredefinedType = request.PredefinedType
        };

        byte[] stampedPdf = await mediator.Send(command, cancellationToken);

        return File(stampedPdf, "application/pdf", "stamped.pdf");
    }

    /// <summary>
    /// Adds a stamp (text, image, or predefined) to PDF pages.
    /// Supports Base64-encoded PDF and image via JSON payload.
    /// </summary>
    /// <param name="request">Request containing Base64-encoded PDF, stamp parameters, and optional Base64 image</param>
    /// <param name="cancellationToken">Cancellation token</param>
    /// <returns>Stamped PDF file</returns>
    /// <response code="200">Stamp added successfully - returns stamped PDF</response>
    /// <response code="400">Invalid input (Base64 format error, invalid page number, missing required parameters)</response>
    /// <response code="500">Internal server error during PDF processing</response>
    [HttpPost("stamp", Name = "AddStampFromBase64")]
    [Consumes("application/json")]
    [ProducesResponseType(typeof(FileContentResult), StatusCodes.Status200OK)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status400BadRequest)]
    [ProducesResponseType(typeof(ProblemDetails), StatusCodes.Status500InternalServerError)]
    public async Task<IActionResult> AddStampFromBase64(
        [FromBody] AddStampBase64Request request,
        CancellationToken cancellationToken)
    {
        // Convert Base64 PDF to MemoryStream
        Stream pdfStream;
        try
        {
            byte[] pdfBytes = Convert.FromBase64String(request.Base64Pdf);
            pdfStream = new MemoryStream(pdfBytes);
        }
        catch (FormatException ex)
        {
            throw new BadRequestException("Invalid Base64 PDF format: " + ex.Message);
        }

        // Convert Base64 image to byte[] if provided
        byte[]? imageBytes = null;
        if (!string.IsNullOrWhiteSpace(request.Base64Image))
        {
            try
            {
                imageBytes = Convert.FromBase64String(request.Base64Image);
            }
            catch (FormatException ex)
            {
                pdfStream.Dispose();
                throw new BadRequestException("Invalid Base64 image format: " + ex.Message);
            }
        }

        var command = new AddStampCommand
        {
            PdfStream = pdfStream,
            StampType = request.StampType,
            PageNumbers = request.PageNumbers,
            Position = (request.X, request.Y),
            Size = request.Width.HasValue && request.Height.HasValue 
                ? (request.Width.Value, request.Height.Value) 
                : null,
            Origin = request.Origin,
            Text = request.Text,
            FontName = request.FontName,
            FontSize = request.FontSize,
            Color = request.Color,
            Opacity = request.Opacity,
            Rotation = request.Rotation,
            Placement = request.Placement,
            ImageBytes = imageBytes,
            PredefinedType = request.PredefinedType
        };

        byte[] stampedPdf = await mediator.Send(command, cancellationToken);

        // Cleanup stream
        pdfStream.Dispose();

        return File(stampedPdf, "application/pdf", "stamped.pdf");
    }
}

/// <summary>
/// Request DTO for multipart/form-data annotation operation
/// </summary>
public class AddAnnotationMultipartRequest
{
    /// <summary>
    /// PDF file to annotate
    /// </summary>
    public required IFormFile File { get; set; }

    /// <summary>
    /// Type of annotation (TextMarkup, FreeText, StickyNote, Circle, Square)
    /// </summary>
    public required AnnotationType AnnotationType { get; set; }

    /// <summary>
    /// Target page number (1-indexed)
    /// </summary>
    public required int PageNumber { get; set; }

    /// <summary>
    /// Rectangle X1 coordinate (left)
    /// </summary>
    public required double X1 { get; set; }

    /// <summary>
    /// Rectangle Y1 coordinate (top or bottom depending on Origin)
    /// </summary>
    public required double Y1 { get; set; }

    /// <summary>
    /// Rectangle X2 coordinate (right). Optional if Width is provided.
    /// </summary>
    public double? X2 { get; set; }

    /// <summary>
    /// Rectangle Y2 coordinate (bottom or top depending on Origin). Optional if Height is provided.
    /// </summary>
    public double? Y2 { get; set; }

    /// <summary>
    /// Rectangle width. Alternative to X2 (X2 = X1 + Width). Optional if X2 is provided.
    /// </summary>
    public double? Width { get; set; }

    /// <summary>
    /// Rectangle height. Alternative to Y2 (Y2 = Y1 + Height). Optional if Y2 is provided.
    /// </summary>
    public double? Height { get; set; }

    /// <summary>
    /// Annotation content (required for FreeText/StickyNote)
    /// </summary>
    public string? Content { get; set; }

    /// <summary>
    /// Author name (optional)
    /// </summary>
    public string? Author { get; set; }

    /// <summary>
    /// Hex color (6 digits, e.g., "FF0000" for red)
    /// </summary>
    public string? Color { get; set; }

    /// <summary>
    /// Markup style (Highlight/Underline/Strikeout, required for TextMarkup)
    /// </summary>
    public TextMarkupStyle? TextMarkupStyle { get; set; }

    /// <summary>
    /// Coordinate origin (BottomLeft = PDF native, TopLeft = UI-friendly). Default: BottomLeft
    /// </summary>
    public AnnotationOrigin Origin { get; set; } = AnnotationOrigin.BottomLeft;
}

/// <summary>
/// Request DTO for Base64-encoded PDF merge operation (API layer only - converts to MergePdfsCommand)
/// </summary>
public record MergePdfsBase64Request
{
    /// <summary>
    /// Array of Base64-encoded PDF files (minimum 2 required)
    /// </summary>
    /// <example>["JVBERi0xLjQK...", "JVBERi0xLjQK..."]</example>
    public required List<string> Base64Pdfs { get; init; }

    /// <summary>
    /// Optional page ranges per PDF (null = all pages). 
    /// Format: "1-3,5" means pages 1, 2, 3, and 5.
    /// If provided, array length must match Base64Pdfs length.
    /// </summary>
    /// <example>["1-2", "1,3,5", null]</example>
    public List<string?>? PageRanges { get; init; }
}

/// <summary>
/// Request DTO for Base64-encoded PDF annotation (API layer only - converts to AddAnnotationCommand)
/// </summary>
public record AddAnnotationBase64Command
{
    /// <summary>
    /// Base64-encoded PDF file
    /// </summary>
    /// <example>"JVBERi0xLjQK..."</example>
    public required string Base64Pdf { get; init; }

    /// <summary>
    /// Type of annotation to add
    /// </summary>
    /// <example>TextMarkup</example>
    public required AnnotationType AnnotationType { get; init; }

    /// <summary>
    /// Target page number (1-indexed)
    /// </summary>
    /// <example>1</example>
    public required int PageNumber { get; init; }

    /// <summary>
    /// Rectangle X1 coordinate (left)
    /// </summary>
    /// <example>100.0</example>
    public required double X1 { get; init; }

    /// <summary>
    /// Rectangle Y1 coordinate (top or bottom depending on Origin)
    /// </summary>
    /// <example>100.0</example>
    public required double Y1 { get; init; }

    /// <summary>
    /// Rectangle X2 coordinate (right). Optional if Width is provided.
    /// </summary>
    /// <example>200.0</example>
    public double? X2 { get; init; }

    /// <summary>
    /// Rectangle Y2 coordinate (bottom or top depending on Origin). Optional if Height is provided.
    /// </summary>
    /// <example>120.0</example>
    public double? Y2 { get; init; }

    /// <summary>
    /// Rectangle width. Alternative to X2 (X2 = X1 + Width). Optional if X2 is provided.
    /// </summary>
    /// <example>100.0</example>
    public double? Width { get; init; }

    /// <summary>
    /// Rectangle height. Alternative to Y2 (Y2 = Y1 + Height). Optional if Y2 is provided.
    /// </summary>
    /// <example>20.0</example>
    public double? Height { get; init; }

    /// <summary>
    /// Annotation content (required for FreeText and StickyNote)
    /// </summary>
    /// <example>"Important text to highlight"</example>
    public string? Content { get; init; }

    /// <summary>
    /// Author name (optional)
    /// </summary>
    /// <example>"John Doe"</example>
    public string? Author { get; init; }

    /// <summary>
    /// Hex color (6 digits, e.g., "FF0000" for red). Optional - defaults vary by annotation type.
    /// </summary>
    /// <example>"FFFF00"</example>
    public string? Color { get; init; }

    /// <summary>
    /// Text markup style (Highlight, Underline, or Strikeout). Required for TextMarkup annotations.
    /// </summary>
    /// <example>Highlight</example>
    public TextMarkupStyle? TextMarkupStyle { get; init; }

    /// <summary>
    /// Coordinate origin (BottomLeft = PDF native, TopLeft = UI-friendly). Default: BottomLeft
    /// </summary>
    /// <example>BottomLeft</example>
    public AnnotationOrigin Origin { get; init; } = AnnotationOrigin.BottomLeft;
}

/// <summary>
/// Request DTO for multipart/form-data stamp operation
/// </summary>
public class AddStampMultipartRequest
{
    /// <summary>
    /// PDF file to stamp
    /// </summary>
    public required IFormFile File { get; set; }

    /// <summary>
    /// Type of stamp (Text, Image, or Predefined)
    /// </summary>
    public required StampType StampType { get; set; }

    /// <summary>
    /// Target page numbers (1-indexed). Null or empty = all pages.
    /// </summary>
    /// <example>[1, 3, 5]</example>
    public int[]? PageNumbers { get; set; }

    /// <summary>
    /// Stamp position X coordinate
    /// </summary>
    /// <example>100.0</example>
    public required double X { get; set; }

    /// <summary>
    /// Stamp position Y coordinate
    /// </summary>
    /// <example>100.0</example>
    public required double Y { get; set; }

    /// <summary>
    /// Stamp width (optional, auto-size for images if not specified)
    /// </summary>
    /// <example>200.0</example>
    public double? Width { get; set; }

    /// <summary>
    /// Stamp height (optional, auto-size for images if not specified)
    /// </summary>
    /// <example>50.0</example>
    public double? Height { get; set; }

    /// <summary>
    /// Coordinate origin (BottomLeft = PDF native, TopLeft = UI-friendly). Default: BottomLeft
    /// </summary>
    /// <example>BottomLeft</example>
    public AnnotationOrigin Origin { get; set; } = AnnotationOrigin.BottomLeft;

    /// <summary>
    /// Text content (required for Text stamps)
    /// </summary>
    /// <example>"CONFIDENTIAL"</example>
    public string? Text { get; set; }

    /// <summary>
    /// Font name (default: Arial)
    /// </summary>
    /// <example>"Arial"</example>
    public string? FontName { get; set; }

    /// <summary>
    /// Font size in points (default: 12)
    /// </summary>
    /// <example>24.0</example>
    public double? FontSize { get; set; }

    /// <summary>
    /// Hex color (6 digits, e.g., "FF0000" for red, default: "000000")
    /// </summary>
    /// <example>"FF0000"</example>
    public string? Color { get; set; }

    /// <summary>
    /// Opacity (0.0 = transparent, 1.0 = opaque, default: 0.5)
    /// </summary>
    /// <example>0.5</example>
    public double? Opacity { get; set; }

    /// <summary>
    /// Rotation angle in degrees (0-360, default: 0)
    /// </summary>
    /// <example>45.0</example>
    public double? Rotation { get; set; }

    /// <summary>
    /// Stamp placement (Foreground = on top, Background = watermark effect, default: Foreground)
    /// </summary>
    /// <example>Foreground</example>
    public StampPlacement Placement { get; set; } = StampPlacement.Foreground;

    /// <summary>
    /// Image file (required for Image stamps, PNG/JPEG)
    /// </summary>
    public IFormFile? ImageFile { get; set; }

    /// <summary>
    /// Predefined stamp type (required for Predefined stamps)
    /// </summary>
    /// <example>Confidential</example>
    public PredefinedStampType? PredefinedType { get; set; }
}

/// <summary>
/// Request DTO for Base64-encoded PDF stamp operation (API layer only - converts to AddStampCommand)
/// </summary>
public record AddStampBase64Request
{
    /// <summary>
    /// Base64-encoded PDF file
    /// </summary>
    /// <example>"JVBERi0xLjQK..."</example>
    public required string Base64Pdf { get; init; }

    /// <summary>
    /// Type of stamp (Text, Image, or Predefined)
    /// </summary>
    /// <example>Text</example>
    public required StampType StampType { get; init; }

    /// <summary>
    /// Target page numbers (1-indexed). Null or empty = all pages.
    /// </summary>
    /// <example>[1, 3, 5]</example>
    public int[]? PageNumbers { get; init; }

    /// <summary>
    /// Stamp position X coordinate
    /// </summary>
    /// <example>100.0</example>
    public required double X { get; init; }

    /// <summary>
    /// Stamp position Y coordinate
    /// </summary>
    /// <example>100.0</example>
    public required double Y { get; init; }

    /// <summary>
    /// Stamp width (optional, auto-size for images if not specified)
    /// </summary>
    /// <example>200.0</example>
    public double? Width { get; init; }

    /// <summary>
    /// Stamp height (optional, auto-size for images if not specified)
    /// </summary>
    /// <example>50.0</example>
    public double? Height { get; init; }

    /// <summary>
    /// Coordinate origin (BottomLeft = PDF native, TopLeft = UI-friendly). Default: BottomLeft
    /// </summary>
    /// <example>BottomLeft</example>
    public AnnotationOrigin Origin { get; init; } = AnnotationOrigin.BottomLeft;

    /// <summary>
    /// Text content (required for Text stamps)
    /// </summary>
    /// <example>"CONFIDENTIAL"</example>
    public string? Text { get; init; }

    /// <summary>
    /// Font name (default: Arial)
    /// </summary>
    /// <example>"Arial"</example>
    public string? FontName { get; init; }

    /// <summary>
    /// Font size in points (default: 12)
    /// </summary>
    /// <example>24.0</example>
    public double? FontSize { get; init; }

    /// <summary>
    /// Hex color (6 digits, e.g., "FF0000" for red, default: "000000")
    /// </summary>
    /// <example>"FF0000"</example>
    public string? Color { get; init; }

    /// <summary>
    /// Opacity (0.0 = transparent, 1.0 = opaque, default: 0.5)
    /// </summary>
    /// <example>0.5</example>
    public double? Opacity { get; init; }

    /// <summary>
    /// Rotation angle in degrees (0-360, default: 0)
    /// </summary>
    /// <example>45.0</example>
    public double? Rotation { get; init; }

    /// <summary>
    /// Stamp placement (Foreground = on top, Background = watermark effect, default: Foreground)
    /// </summary>
    /// <example>Foreground</example>
    public StampPlacement Placement { get; init; } = StampPlacement.Foreground;

    /// <summary>
    /// Base64-encoded image (required for Image stamps, PNG/JPEG)
    /// </summary>
    /// <example>"iVBORw0KGgoAAAANSUhEUgAA..."</example>
    public string? Base64Image { get; init; }

    /// <summary>
    /// Predefined stamp type (required for Predefined stamps)
    /// </summary>
    /// <example>Confidential</example>
    public PredefinedStampType? PredefinedType { get; init; }
}
