using System;
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Globalization;
using System.IO;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
using SixLabors.ImageSharp.Metadata.Profiles.Exif;

// Imports System.Threading

namespace MaddoShared;

[SuppressMessage("Interoperability", "CA1416:Validate platform compatibility")]
public class ImageCreatorSharp(PicSettings picSettings, ILogger<ImageCreatorSharp> logger) : IImageCreator
{
    public async Task CreateImageAsync(ImageState imgState, Image logo)
    {
        try
        {
            await Task.Run(() =>
            {
                logger.LogInformation("File: {FileInfo} Dest: {DirectoryInfo}", imgState.WorkFile, imgState.DestDir);
                PrepareVariables(imgState);
                ExtractExif(imgState);

                using var g = Image.FromFile(imgState.WorkFile.FullName);

                // Set extra text
                SetExtraText(g, imgState);

                // Rotate image according to EXIF
                ApplyRotation(g, imgState);

                // Force jpeg if option selected
                var thisFormat = g.RawFormat;
                if (picSettings.UsaForzaJpg)
                    thisFormat = ImageFormat.Jpeg;

                PrepareThumbnailSize(g, imgState);

                using var imgOutputBig = new Bitmap(g, imgState.ThumbSizeBig.Width, imgState.ThumbSizeBig.Height);

                imgOutputBig.SetResolution(g.HorizontalResolution, g.VerticalResolution);

                // Create thumbnails
                CreateThumbnails(g, imgState, imgOutputBig, thisFormat);

                AddText(g, imgState, imgOutputBig);

                AddLogo(imgOutputBig, logo);

                SavePhoto(imgOutputBig, imgState, thisFormat);
            }).ConfigureAwait(false);
        }

        catch (Exception ex)
        {
            var e = ex.Demystify();
            logger.LogError(e, "Error in processing photo {WorkFileName}", imgState.WorkFile.Name);
        }
    }

    private void ExtractExif(ImageState imgState)
    {
        using var img = SixLabors.ImageSharp.Image.Load(imgState.WorkFile.FullName);
        imgState.Orientation = Orientations.TopLeft;

        IExifValue<ushort> rotation = null;

        var exifProfile = img.Metadata?.ExifProfile;
        var found = exifProfile != null && exifProfile.TryGetValue(ExifTag.Orientation, out rotation);

        if (found)
        {
            var intOrientation = rotation.Value.ToInt32();
            imgState.Orientation = (Orientations)intOrientation;
        }

        IExifValue<string> date = null;
        var creationFound = exifProfile != null && exifProfile.TryGetValue(ExifTag.DateTimeOriginal, out date);
        if (creationFound)
        {
            var succ = DateTime.TryParseExact(date.Value, "yyyy:MM:dd HH:mm:ss", CultureInfo.InvariantCulture,
                DateTimeStyles.None, out var crDate);
            if (succ)
            {
                imgState.CreationDate = crDate;
            }
            else
            {
                imgState.CreationDate = null;
            }
        }
        else
        {
            imgState.CreationDate = null;
        }
    }

    private void ApplyRotation(Image g, ImageState imgState)
    {
        imgState.FotoRuotaADestra = false;
        imgState.FotoRuotaASinistra = false;

        if (picSettings.UsaRotazioneAutomatica && g.PropertyIdList.Length > 0)
        {
            switch (imgState.Orientation)
            {
                case Orientations.BottomLeft:
                case Orientations.BottomRight:
                case Orientations.LeftTop:
                case Orientations.LftBottom:
                    imgState.FotoRuotaASinistra = true;
                    break;
                case Orientations.RightBottom:
                case Orientations.RightTop:
                case Orientations.TopLeft:
                case Orientations.TopRight:
                    break;
            }
        }

        if (imgState.FotoRuotaASinistra)
            g.RotateFlip(RotateFlipType.Rotate270FlipNone);
        if (imgState.FotoRuotaADestra)
            g.RotateFlip(RotateFlipType.Rotate90FlipNone);
    }

    /// <summary>
    ///     ''' Aggiunge Orario, tempo gara e altri
    ///     ''' </summary>
    ///     ''' <param name="g">Image</param>
    ///     <param name="imgState"></param>
    ///     ''' <remarks></remarks>
    private void SetExtraText(Image g, ImageState imgState)
    {
        if (picSettings.UsaOrarioTestoApplicare || picSettings.UsaTempoGaraTestoApplicare ||
            picSettings.UsaOrarioMiniatura || picSettings.TestoMin || picSettings.AggTempoGaraMin ||
            picSettings.AggNumTempMin)
        {
            if (g.PropertyIdList.Length <= 0) return;
            imgState.DataFoto = imgState.CreationDate ?? DateTime.Now;
            imgState.TestoFirma = picSettings.TestoFirmaStart;
            imgState.TestoFirmaV = picSettings.TestoFirmaStartV;

            if (imgState.DataFoto.Year == 1) return;
            imgState.TestoFirmaPiccola = imgState.DataFoto.ToShortTimeString();
            if (picSettings.UsaOrarioTestoApplicare)
            {
                imgState.TestoFirma +=
                    $" {imgState.DataFoto.ToShortDateString()} {imgState.DataFoto.ToLongTimeString()}";
                imgState.TestoFirmaV +=
                    $" {imgState.DataFoto.ToShortDateString()} {imgState.DataFoto.ToLongTimeString()}";
            }

            if (!picSettings.UsaTempoGaraTestoApplicare) return;
            var diff = imgState.DataFoto - imgState.DataPartenzaI;
            imgState.TestoFirma += $" {imgState.TestoOrario}{diff.Hours:00}:{diff.Minutes:00}:{diff.Seconds:00}";
            imgState.TestoFirmaV += $" {imgState.TestoOrario}{diff.Hours:00}:{diff.Minutes:00}:{diff.Seconds:00}";
        }
        else
        {
            imgState.TestoFirma = picSettings.TestoFirmaStart;
            imgState.TestoFirmaV = picSettings.TestoFirmaStartV;
        }
    }

    /// <summary>
    ///     ''' Prepara diverse variabili azzerandole, elaborandole e prendendole dalle impostazioni
    ///     ''' </summary>
    ///     ''' <remarks></remarks>
    private void PrepareVariables(ImageState imgState)
    {
        imgState.AlphaScelta = System.Convert.ToInt32((255 * (100 - picSettings.Trasparenza) / (double)100));
        imgState.TestoFirma = "";
        imgState.TestoFirmaV = "";
        imgState.DataPartenzaI = picSettings.DataPartenza;
        imgState.TestoOrario = picSettings.TestoOrario;
        if (imgState.TestoOrario.Length > 0)
            imgState.TestoOrario += " ";
        imgState.TestoFirmaPiccola = "";
        imgState.ThumbSizeSmall = new Size();
        imgState.ThumbSizeBig = new Size();
        imgState.NomeFileSmall = "";
        imgState.NomeFileBig2 = "";
        imgState.NomeFileBig = "";
        imgState.DimensioneStandard = picSettings.DimStandard;
        imgState.DimensioneStandardMiniatura = picSettings.DimStandardMiniatura;
        // nomeFileSmall = Suffisso & NomeFileChild
        // nomeFileBig = NomeFileChild
        imgState.NomeFileSmall = picSettings.Suffisso + imgState.WorkFile.Name;
        imgState.NomeFileBig = imgState.WorkFile.Name;
        // Sanitize file names to avoid invalid characters causing IO errors
        imgState.NomeFileSmall = SanitizeFileName(imgState.NomeFileSmall);
        imgState.NomeFileBig = SanitizeFileName(imgState.NomeFileBig);
    }

    private static string SanitizeFileName(string fileName)
    {
        if (string.IsNullOrEmpty(fileName)) return fileName;
        var invalid = Path.GetInvalidFileNameChars();
        var sb = new System.Text.StringBuilder(fileName.Length);
        foreach (var ch in fileName)
        {
            sb.Append(Array.IndexOf(invalid, ch) >= 0 ? '_' : ch);
        }
        return sb.ToString();
    }

    private void PrepareThumbnailSize(Image g, ImageState imgState)
    {
        if (g.Width > g.Height)
        {
            imgState.ThumbSizeSmall = CalculateThumbnailSize(g.Width, g.Height, picSettings.LarghezzaSmall, "Larghezza");
            var sizeOrig = new Size(g.Width, g.Height);
            imgState.ThumbSizeBig = sizeOrig;
        }
        else
        {
            imgState.ThumbSizeSmall = CalculateThumbnailSize(g.Width, g.Height, picSettings.AltezzaSmall, "Altezza");
            var sizeOrig = new Size(g.Width, g.Height);
            imgState.ThumbSizeBig = sizeOrig;
        }
    }

        private void CreateThumbnails(Image sourceImage, ImageState imgState, Bitmap imgOutputBig, ImageFormat format)
        {
            // Only skip thumbnail generation when the global "create thumbnails" flag is false.
            // Whether thumbnails include text is handled by ShouldRenderText/CreateThumbnailWithText
            if (!picSettings.CreaMiniature)
                return;

            PrepareSignatureText(imgState);

            if (IsSameDirectory(picSettings.DirectorySorgente, picSettings.DirectoryDestinazione))
                UpdateFilenameWithCode(imgState);

            if (ShouldRenderText())
                CreateThumbnailWithText(sourceImage, imgState, imgOutputBig, format);
            else
                CreateSimpleThumbnail(sourceImage, imgState, format);
        }

    private void PrepareSignatureText(ImageState imgState)
    {
        if (picSettings.TestoMin)
            imgState.TestoFirmaPiccola = imgState.NomeFileBig;
        else if (picSettings.AggNumTempMin)
            imgState.TestoFirmaPiccola = imgState.NomeFileBig + " ";
    }

    private bool IsSameDirectory(string dir1, string dir2) =>
        string.Equals(dir1, dir2, StringComparison.OrdinalIgnoreCase);

    private void UpdateFilenameWithCode(ImageState imgState)
    {
        var name = imgState.NomeFileSmall;
        imgState.NomeFileSmall = name[..^4] + picSettings.Codice + name[^4..];
    }

    private bool ShouldRenderText() =>
        picSettings.UsaOrarioMiniatura || picSettings.TestoMin || picSettings.AggTempoGaraMin ||
        picSettings.AggNumTempMin;

    private void CreateSimpleThumbnail(Image image, ImageState imgState, ImageFormat format)
    {
        using var thumbnail = new Bitmap(image, imgState.ThumbSizeSmall.Width, imgState.ThumbSizeSmall.Height);
        thumbnail.Save(Path.Combine(imgState.DestDir.FullName, imgState.NomeFileSmall), format);
    }

    private void CreateThumbnailWithText(Image image, ImageState imgState, Bitmap sourceBitmap, ImageFormat format)
    {
        if (imgState.TestoFirmaPiccola.Length == 0)
        {
            CreateSimpleThumbnail(image, imgState, format);
            return;
        }

        using var imgOutputSmall = (Bitmap)sourceBitmap.Clone();
        using var graphics = Graphics.FromImage(imgOutputSmall);
        graphics.SmoothingMode = SmoothingMode.AntiAlias;

        // Use the user's configured font size directly
        using var font1 = CreateFont(picSettings.IlFont, imgState.DimensioneStandardMiniatura, picSettings.Grassetto);
        var textSize = graphics.MeasureString(imgState.TestoFirmaPiccola, font1);

        // Adjust font if it's too large for the image dimensions
        // Keep text height under 15% of image height to ensure proper spacing when resized
        // This leaves room for margins and prevents clipping
        int tempFontSize = imgState.DimensioneStandardMiniatura;
        float maxTextHeight = image.Height * 0.15f;
        
        while ((textSize.Width > image.Width * 0.95f || textSize.Height > maxTextHeight) && tempFontSize > 5)
        {
            tempFontSize = (tempFontSize > 20) ? tempFontSize - 5 : tempFontSize - 1;
            using var tempFont = CreateFont(picSettings.IlFont, tempFontSize, picSettings.Grassetto);
            textSize = graphics.MeasureString(imgState.TestoFirmaPiccola, tempFont);
        }

        // Re-measure text with the final font size for accurate positioning
        using var finalFont = CreateFont(picSettings.IlFont, tempFontSize, picSettings.Grassetto);
        var finalTextSize = graphics.MeasureString(imgState.TestoFirmaPiccola, finalFont);

        SetVerticalPosition(image.Height, finalTextSize.Height, imgState);

        float xCenter = CalculateHorizontalAlignment(image.Width, finalTextSize.Width);
        using var stringFormat = new StringFormat();
        stringFormat.Alignment = StringAlignment.Center;

        using var shadowBrush = new SolidBrush(Color.FromArgb(imgState.AlphaScelta, 0, 0, 0));
        using var textBrush = new SolidBrush(Color.FromArgb(imgState.AlphaScelta, picSettings.FontColoreRGB));

        DrawText(graphics, imgState, xCenter, stringFormat, shadowBrush, textBrush, finalFont);

        using var finalThumb =
            new Bitmap(imgOutputSmall, imgState.ThumbSizeSmall.Width, imgState.ThumbSizeSmall.Height);
        finalThumb.Save(Path.Combine(imgState.DestDir.FullName, imgState.NomeFileSmall), format);
    }

    private Font CreateFont(string fontName, int size, bool bold) =>
        new Font(fontName, size, bold ? FontStyle.Bold : FontStyle.Regular);

    private int FindBestFontSize(Graphics g, string text, string fontName, int maxSize, bool bold, int maxWidth, int minSize = 5)
    {
        if (maxSize <= minSize) return Math.Max(minSize, maxSize);

        int low = minSize;
        int high = Math.Max(minSize, maxSize);
        int best = minSize;

        while (low <= high)
        {
            int mid = (low + high) / 2;
            using var testFont = CreateFont(fontName, mid, bold);
            var measured = g.MeasureString(text, testFont);
            if (measured.Width <= maxWidth)
            {
                best = mid;
                low = mid + 1; // try larger
            }
            else
            {
                high = mid - 1; // too big
            }
        }

        return best;
    }


    private void AddText(Image g, ImageState imgState, Bitmap imgOutputBig)
    {
        using var grPhoto = Graphics.FromImage(imgOutputBig);
        grPhoto.SmoothingMode = SmoothingMode.AntiAlias;

        // Determine best base font size using a binary search (faster than decremental loop)
        int availableWidth = (int)g.Width;
        int targetBaseSize = imgState.DimensioneStandard > 0 ? imgState.DimensioneStandard : picSettings.DimStandard;
        int bestBaseSize = FindBestFontSize(grPhoto, imgState.TestoFirma ?? string.Empty, picSettings.IlFont, targetBaseSize, picSettings.Grassetto, availableWidth);
        imgState.DimensioneStandard = bestBaseSize;

        // Decide final drawing size (use DimVert if rotated)
        int drawSize = (imgState.FotoRuotaADestra || imgState.FotoRuotaASinistra) ? picSettings.DimVert : imgState.DimensioneStandard;

        using var drawFont = CreateFont(picSettings.IlFont, drawSize, picSettings.Grassetto);
        var crSize = grPhoto.MeasureString(imgState.TestoFirma ?? string.Empty, drawFont);
        var larghezzaStandard = Convert.ToInt32(crSize.Width);

        // Vertical positions
        switch (picSettings.Posizione.ToUpper())
        {
            case "ALTO":
            {
                imgState.YPosFromBottom = picSettings.Margine;
                imgState.YPosFromBottom3 = picSettings.MargVert;
                break;
            }

            case "BASSO":
            {
                imgState.YPosFromBottom =
                    Convert.ToSingle((g.Height - crSize.Height - (g.Height * picSettings.Margine / 100.0)));
                imgState.YPosFromBottom3 =
                    Convert.ToSingle((g.Height - crSize.Height - (g.Height * picSettings.MargVert / 100.0)));
                break;
            }
        }

        float xCenterOfImg = 0;
        using var strFormat = new StringFormat();
        switch (picSettings.Allineamento.ToUpper())
        {
            case "SINISTRA":
            {
                xCenterOfImg = Convert.ToSingle((picSettings.Margine + (larghezzaStandard / (double)2)));
                if ((larghezzaStandard / (double)2) > (g.Width / (double)2) - picSettings.Margine)
                    xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
                break;
            }

            case "CENTRO":
            {
                xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
                break;
            }

            case "DESTRA":
            {
                xCenterOfImg =
                    Convert.ToSingle((g.Width - picSettings.Margine - (larghezzaStandard / (double)2)));
                if ((larghezzaStandard / (double)2) > (g.Width / (double)2) - picSettings.Margine)
                    xCenterOfImg = Convert.ToSingle((g.Width / (double)2));
                break;
            }
        }

        strFormat.Alignment = StringAlignment.Center;

        using var semiTransBrush2 = new SolidBrush(Color.FromArgb(imgState.AlphaScelta, 0, 0, 0));
        using var semiTransBrush = new SolidBrush(Color.FromArgb(imgState.AlphaScelta, picSettings.FontColoreRGB));

        // write text (NomeFileBig)
        if (picSettings.TestoNome)
        {
            if (picSettings.NomeData && g.PropertyIdList.Length > 0)
            {
                imgState.DataFoto = imgState.CreationDate ?? DateTime.Now;

                grPhoto.DrawString((imgState.NomeFileBig + " " + imgState.DataFoto.ToShortDateString()), drawFont,
                    semiTransBrush2, new PointF(xCenterOfImg + 1, imgState.YPosFromBottom + 1), strFormat);
                grPhoto.DrawString((imgState.NomeFileBig + " " + imgState.DataFoto.ToShortDateString()), drawFont,
                    semiTransBrush, new PointF(xCenterOfImg, imgState.YPosFromBottom), strFormat);
            }
            else
            {
                grPhoto.DrawString(imgState.NomeFileBig, drawFont, semiTransBrush2,
                    new PointF(xCenterOfImg + 1, imgState.YPosFromBottom + 1), strFormat);
                grPhoto.DrawString(imgState.NomeFileBig, drawFont, semiTransBrush,
                    new PointF(xCenterOfImg, imgState.YPosFromBottom), strFormat);
            }
        }
        else
        {
            if (imgState.FotoRuotaADestra || imgState.FotoRuotaASinistra)
            {
                if (!picSettings.TestoMin)
                {
                    grPhoto.DrawString(imgState.TestoFirmaV, drawFont, semiTransBrush2,
                        new PointF(xCenterOfImg + 1, imgState.YPosFromBottom3 + 1), strFormat);
                    grPhoto.DrawString(imgState.TestoFirmaV, drawFont, semiTransBrush,
                        new PointF(xCenterOfImg, imgState.YPosFromBottom3), strFormat);
                }

                if (picSettings.TestoMin)
                {
                    grPhoto.DrawString(imgState.TestoFirmaV, drawFont, semiTransBrush2,
                        new PointF(xCenterOfImg + 1, imgState.YPosFromBottom4 + 1), strFormat);
                    grPhoto.DrawString(imgState.TestoFirmaV, drawFont, semiTransBrush,
                        new PointF(xCenterOfImg, imgState.YPosFromBottom4), strFormat);
                }
            }
            else
            {
                grPhoto.DrawString(imgState.TestoFirma, drawFont, semiTransBrush2,
                    new PointF(xCenterOfImg + 1, imgState.YPosFromBottom + 1), strFormat);
                grPhoto.DrawString(imgState.TestoFirma, drawFont, semiTransBrush,
                    new PointF(xCenterOfImg, imgState.YPosFromBottom), strFormat);
            }
        }

        if (string.Equals(picSettings.DirectorySorgente, picSettings.DirectoryDestinazione,
                StringComparison.OrdinalIgnoreCase))
        {
            imgState.NomeFileBig2 = imgState.NomeFileBig;
            imgState.NomeFileBig = $"{imgState.NomeFileBig[..^4]}{picSettings.Codice}{imgState.NomeFileBig[^4..]}";
        }
    }

    private void AddLogo(Bitmap imgOutputBig, Image logo)
    {
        // Skip if no logo provided
        if (logo is null) return;

        // Load check (use short-circuit &&)
        if (!(picSettings.LogoAggiungi && File.Exists(picSettings.LogoNomeFile))) return;

        // Decide whether to apply a color-key transparency remap or rely on existing image alpha.
        // If UseTransparentColor is true, parse the configured TransparentColor and remap it to fully transparent.
        using var grWatermark = Graphics.FromImage(imgOutputBig);
        using ImageAttributes imageAttributes = new ImageAttributes();

        if (picSettings.UseTransparentColor)
        {
            Color keyColor = Color.White;
            try
            {
                if (!string.IsNullOrWhiteSpace(picSettings.TransparentColor))
                {
                    // ColorTranslator accepts both "#RRGGBB" and "RRGGBB"
                    keyColor = ColorTranslator.FromHtml(picSettings.TransparentColor);
                }
            }
            catch
            {
                keyColor = Color.White;
            }

            var colorMap = new ColorMap
            {
                // background: the color we search for and replace with transparency
                OldColor = keyColor,
                NewColor = Color.FromArgb(0, 0, 0, 0)
            };

            var remapTable = new[] { colorMap };
            imageAttributes.SetRemapTable(remapTable, ColorAdjustType.Bitmap);
        }

        // * The second color manipulation is used to change the opacity by setting the 3rd row and 3rd column to 0.3f
        // Parse transparency safely (default to 100 if parsing fails)
        if (!int.TryParse(picSettings.LogoTrasparenza, out var logoTransparencyValue))
        {
            logoTransparencyValue = 100;
        }
        
        var colorMatrixElements = new[]
        {
            new[] { 1.0F, 0.0F, 0.0F, 0.0F, 0.0F }, new[] { 0.0F, 1.0F, 0.0F, 0.0F, 0.0F },
            new[] { 0.0F, 0.0F, 1.0F, 0.0F, 0.0F },
            new[] { 0.0F, 0.0F, 0.0F, System.Convert.ToSingle(logoTransparencyValue) / 100F, 0.0F },
            new[] { 0.0F, 0.0F, 0.0F, 0.0F, 1.0F }
        };
        var wmColorMatrix = new ColorMatrix(colorMatrixElements);
        imageAttributes.SetColorMatrix(wmColorMatrix, ColorMatrixFlag.Default, ColorAdjustType.Bitmap);

        var fotoLogoH = picSettings.LogoAltezza;
        var fotoLogoW = picSettings.LogoLarghezza;
        var fattoreAlt = logo.Height / (double)fotoLogoH;
        var fattoreLarg = logo.Width / (double)fotoLogoW;
        var nuovaSize = fattoreLarg > fattoreAlt
            ? CalculateThumbnailSize(logo.Width, logo.Height, fotoLogoW, "Larghezza")
            : CalculateThumbnailSize(logo.Width, logo.Height, fotoLogoH, "Altezza");

        // Guard against null/empty LogoMargine and percentage parsing
        var logoMargineStr = picSettings.LogoMargine ?? string.Empty;
        var inPercentualeL = logoMargineStr.EndsWith('%');
        var margineL = 0;
        if (inPercentualeL)
        {
            var trimmed = logoMargineStr.TrimEnd('%');
            if (!int.TryParse(trimmed, out margineL)) margineL = 0;
        }
        else
        {
            if (!int.TryParse(logoMargineStr, out margineL)) margineL = 0;
        }
        var margineUsato =
            inPercentualeL ? System.Convert.ToInt32(imgOutputBig.Height * margineL / (double)100) : margineL;

        int xPosOfWm = 0;
        int yPosOfWm = 0;
        var logoH = (picSettings.LogoPosizioneH ?? "NESSUNA").ToUpperInvariant();
        var logoV = (picSettings.LogoPosizioneV ?? "NESSUNA").ToUpperInvariant();
        switch (logoH)
        {
            case "SINISTRA":
            case "NESSUNA":
            {
                xPosOfWm = margineUsato;
                break;
            }

            case "CENTRO":
            {
                xPosOfWm = System.Convert.ToInt32((imgOutputBig.Width - nuovaSize.Width) / (double)2);
                break;
            }

            case "DESTRA":
            {
                xPosOfWm = ((imgOutputBig.Width - nuovaSize.Width) - margineUsato);
                break;
            }
        }

        switch (logoV)
        {
            case "ALTO":
            case "NESSUNA":
            {
                yPosOfWm = margineUsato;
                break;
            }

            case "CENTRO":
            {
                yPosOfWm = System.Convert.ToInt32((imgOutputBig.Height - nuovaSize.Height) / (double)2);
                break;
            }

            case "BASSO":
            {
                yPosOfWm = ((imgOutputBig.Height - nuovaSize.Height) - margineUsato);
                break;
            }
        }

        grWatermark.DrawImage(logo, new Rectangle(xPosOfWm, yPosOfWm, nuovaSize.Width, nuovaSize.Height), 0, 0,
            logo.Width, logo.Height, GraphicsUnit.Pixel, imageAttributes);
        //grWatermark.Dispose();
    }


    private void SavePhoto(Bitmap imgOutputBig, ImageState imgState, ImageFormat thisFormat)
    {
        var fileName = Path.Combine(imgState.DestDir.FullName, imgState.NomeFileBig);

        using var image1Stream = new MemoryStream();
        if (picSettings.FotoGrandeDimOrigina == false)
        {
            // attenzione non controlla se è png
            // imgOutputBig.Save(Path.Combine(_DestDir.FullName, "Temp_" & NomeFileBig), thisFormat)
            if (thisFormat.Equals(ImageFormat.Jpeg))
            {
                MakeImageCustomQuality(imgOutputBig, image1Stream, picSettings.JpegQuality);
            }
            //SalvaImmagineCustomQuality(imgOutputBig, Path.Combine(DestDir.FullName, "Temp_" + NomeFileBig), _picSettings.jpegQuality);
            else
            {
                imgOutputBig.Save(image1Stream, thisFormat);
            }

            //imgOutputBig.Save(Path.Combine(DestDir.FullName, "Temp_" + NomeFileBig), thisFormat);
            image1Stream.Seek(0, SeekOrigin.Begin);
            using var g2 = Image.FromStream(image1Stream);
            imgState.ThumbSizeBig = g2.Width > g2.Height
                ? CalculateThumbnailSize(g2.Width, g2.Height, picSettings.LarghezzaBig, "Larghezza")
                : CalculateThumbnailSize(g2.Width, g2.Height, picSettings.AltezzaBig, "Altezza");
            using var imgOutputBig2 = new Bitmap(g2, imgState.ThumbSizeBig.Width, imgState.ThumbSizeBig.Height);

            if (!picSettings.OverwriteFiles && File.Exists(fileName))
            {
                logger.LogInformation("Saltata foto {FileName}, esiste", fileName);
            }
            else
            {
                if (thisFormat.Equals(ImageFormat.Jpeg))
                    SaveImageCustomQuality(imgOutputBig2, fileName, picSettings.JpegQuality);
                else
                    imgOutputBig2.Save(fileName, thisFormat);
            }
        }
        else
        {
            if (!picSettings.OverwriteFiles && File.Exists(fileName))
            {
                logger.LogInformation("Saltata foto {FileName}, esiste", fileName);
            }
            else
            {
                if (thisFormat.Equals(ImageFormat.Jpeg))
                    SaveImageCustomQuality(imgOutputBig, fileName, picSettings.JpegQuality);
                else
                    imgOutputBig.Save(fileName, thisFormat);
            }
        }

        image1Stream.Seek(0, SeekOrigin.Begin);

        if (!picSettings.CreaMiniature) return;
        if (!picSettings.AggiungiScritteMiniature) return;

        using var g1 = picSettings.FotoGrandeDimOrigina ? (Image)imgOutputBig.Clone() : Image.FromStream(image1Stream);

        using var imgOutputSmall = new Bitmap(g1, imgState.ThumbSizeSmall.Width, imgState.ThumbSizeSmall.Height);

        if (string.Equals(picSettings.DirectorySorgente, picSettings.DirectoryDestinazione,
                StringComparison.OrdinalIgnoreCase))
            imgState.NomeFileSmall = imgState.NomeFileSmall.Substring(0, imgState.NomeFileSmall.Length - 4) +
                                     picSettings.Codice +
                                     imgState.NomeFileSmall.Substring(imgState.NomeFileSmall.Length - 4);

        var tnFileName = Path.Combine(imgState.DestDir.FullName, imgState.NomeFileSmall);

        if (!picSettings.OverwriteFiles && File.Exists(tnFileName))
        {
            logger.LogInformation("Saltata miniatura foto {TnFileName}, esiste", tnFileName);
        }
        else
        {
            if (thisFormat.Equals(ImageFormat.Jpeg))
                SaveImageCustomQuality(imgOutputSmall, tnFileName, picSettings.JpegQualityMin);
            else
                imgOutputSmall.Save(tnFileName, thisFormat);
        }
    }

    private void SaveImageCustomQuality(Bitmap imageToSave, string nomeFileFinale, long quality)
    {
        var jgpEncoder = GetEncoder(ImageFormat.Jpeg);
        var myEncoder = System.Drawing.Imaging.Encoder.Quality;

        using var myEncoderParameters = new EncoderParameters(1);

        var myEncoderParameter = new EncoderParameter(myEncoder, quality);
        myEncoderParameters.Param[0] = myEncoderParameter;
        imageToSave.Save(nomeFileFinale, jgpEncoder, myEncoderParameters);
        //imageToSave.Dispose();
    }

    private void MakeImageCustomQuality(Bitmap imageToSave, Stream destinationStream, long quality)
    {
        var jgpEncoder = GetEncoder(ImageFormat.Jpeg);
        var myEncoder = System.Drawing.Imaging.Encoder.Quality;

        using var myEncoderParameters = new EncoderParameters(1);

        var myEncoderParameter = new EncoderParameter(myEncoder, quality);
        myEncoderParameters.Param[0] = myEncoderParameter;
        destinationStream.Seek(0, SeekOrigin.Begin);
        imageToSave.Save(destinationStream, jgpEncoder, myEncoderParameters);
        //imageToSave.Dispose();
    }

    private ImageCodecInfo GetEncoder(ImageFormat format)
    {
        var codecs = ImageCodecInfo.GetImageDecoders();

        foreach (var codec in codecs)
        {
            if (codec.FormatID == format.Guid)
                return codec;
        }

        return null /* TODO Change to default(_) if this is not a reference type */;
    }

    /// <summary>
    ///     ''' Calculate the Size of the New image
    ///     ''' </summary>
    ///     ''' <param name="currentwidth">Larghezza</param>
    ///     ''' <param name="currentheight">Altezza</param>
    ///     ''' <param name="maxPixel"></param>
    ///     ''' <param name="tipoSize"></param>
    ///     ''' <returns></returns>
    ///     ''' <remarks></remarks>
    private Size CalculateThumbnailSize(int currentwidth, int currentheight, int maxPixel, string tipoSize)
    {
        // e  
        // *** Larghezza, Altezza, Auto

        double tempMultiplier;

        if (tipoSize.ToUpper() == "Larghezza".ToUpper())
            tempMultiplier = maxPixel / (double)currentwidth;
        else if (tipoSize.ToUpper() == "Altezza".ToUpper())
            tempMultiplier = maxPixel / (double)currentheight;
        else if (currentheight > currentwidth)
            tempMultiplier = maxPixel / (double)currentheight;
        else
            tempMultiplier = maxPixel / (double)currentwidth;

        var newSize = new Size(System.Convert.ToInt32(currentwidth * tempMultiplier),
            System.Convert.ToInt32(currentheight * tempMultiplier));

        return newSize;
    }

    private void SetVerticalPosition(int imgHeight, float textHeight, ImageState imgState)
    {
        // Use 1% of image height as minimum margin, or 10px, whichever is larger
        float minMargin = Math.Max(10f, imgHeight * 0.01f);
        
        switch (picSettings.Posizione.ToUpper())
        {
            case "ALTO":
                imgState.YPosFromBottom1 = Math.Max(minMargin, picSettings.Margine);
                imgState.YPosFromBottom4 = Math.Max(minMargin, picSettings.MargVert);
                break;

            case "BASSO":
                var bottomMargin1 = (float)(imgHeight * picSettings.Margine / 100.0);
                var bottomMargin4 = (float)(imgHeight * picSettings.MargVert / 100.0);
                
                // Position from bottom: bottom edge of text at desired margin from bottom
                // Y = imageHeight - textHeight - bottomMargin
                var desiredY1 = imgHeight - textHeight - bottomMargin1;
                var desiredY4 = imgHeight - textHeight - bottomMargin4;
                
                // Ensure text stays completely within bounds:
                // - Top edge must be >= minMargin (not clipped at top)
                // - Bottom edge must be <= imgHeight - minMargin (not clipped at bottom)
                var maxAllowedY1 = imgHeight - textHeight - minMargin; // Maximum Y to keep bottom margin
                var maxAllowedY4 = imgHeight - textHeight - minMargin;
                
                imgState.YPosFromBottom1 = Math.Max(minMargin, Math.Min(desiredY1, maxAllowedY1));
                imgState.YPosFromBottom4 = Math.Max(minMargin, Math.Min(desiredY4, maxAllowedY4));
                break;

            case "CENTRO":
            default:
                // Center the text vertically
                var centeredY = (imgHeight - textHeight) / 2f;
                // Clamp to ensure margins are respected
                imgState.YPosFromBottom1 = Math.Max(minMargin, Math.Min(centeredY, imgHeight - textHeight - minMargin));
                imgState.YPosFromBottom4 = imgState.YPosFromBottom1;
                break;
        }
    }

    private float CalculateHorizontalAlignment(int imgWidth, float textWidth)
    {
        double halfWidth = textWidth / 2.0;

        return picSettings.Allineamento.ToUpper() switch
        {
            "SINISTRA" => (float)Math.Min(picSettings.Margine + halfWidth, imgWidth / 2.0),
            "DESTRA" => (float)Math.Max(imgWidth - picSettings.Margine - halfWidth, imgWidth / 2.0),
            _ => imgWidth / 2.0f, // CENTRO or default
        };
    }

    private void DrawText(Graphics g, ImageState imgState, float x, StringFormat format,
        Brush shadowBrush, Brush textBrush, Font font)
    {
        string content = imgState.TestoFirmaPiccola;

        if (picSettings.TestoMin)
        {
            content = imgState.NomeFileBig;
        }
        else if (picSettings.AggTempoGaraMin && picSettings.UsaTempoGaraTestoApplicare)
        {
            content = FormatTimeText(imgState, includeFileName: false);
        }
        else if (picSettings.AggNumTempMin)
        {
            content = FormatTimeText(imgState, includeFileName: true);
        }

        var offset = new PointF(x + 1, imgState.YPosFromBottom1 + 1);
        var actual = new PointF(x, imgState.YPosFromBottom1);

        g.DrawString(content, font, shadowBrush, offset, format);
        g.DrawString(content, font, textBrush, actual, format);
    }

    private string FormatTimeText(ImageState imgState, bool includeFileName)
    {
        var diff = imgState.DataPartenzaI - imgState.DataFoto;
        var ticks = (long)(diff.TotalSeconds * 10000000);
        var time = new TimeSpan(ticks);

        var formatted = $"{imgState.TestoOrario}{time:hh\\:mm\\:ss}";
        return includeFileName
            ? $"{imgState.NomeFileBig}{Environment.NewLine}{formatted}"
            : Environment.NewLine + formatted;
    }
}
