using RolemasterDb.ImportTool.Parsing;

namespace RolemasterDb.ImportTool;

public sealed class CriticalSourceImageArtifactGenerator(PdfXmlExtractor pdfXmlExtractor)
{
    private const int CropPaddingX = 12;
    private const int CropPaddingY = 8;

    public async Task GenerateAsync(
        string pdfPath,
        ImportArtifactPaths artifactPaths,
        CriticalTableParseResult parseResult,
        CancellationToken cancellationToken = default)
    {
        Directory.CreateDirectory(artifactPaths.PagesDirectoryPath);
        Directory.CreateDirectory(artifactPaths.CellsDirectoryPath);

        var pageGeometriesByNumber = parseResult.PageGeometries.ToDictionary(item => item.PageNumber);
        foreach (var pageGeometry in parseResult.PageGeometries.OrderBy(item => item.PageNumber))
        {
            await pdfXmlExtractor.RenderPagePngAsync(
                pdfPath,
                pageGeometry.PageNumber,
                artifactPaths.GetPageImagePath(pageGeometry.PageNumber),
                parseResult.RenderProfile.RenderDpi,
                cancellationToken);
        }

        var cellsByKey = parseResult.Cells.ToDictionary(
            item => CreateCellKey(item.GroupKey, item.RollBandLabel, item.ColumnKey),
            StringComparer.Ordinal);

        foreach (var result in parseResult.Table.Results)
        {
            if (!pageGeometriesByNumber.TryGetValue(result.SourceBounds.PageNumber, out var pageGeometry))
            {
                throw new InvalidOperationException(
                    $"Missing page geometry for page {result.SourceBounds.PageNumber} in table '{parseResult.Table.Slug}'.");
            }

            var crop = CreateCrop(result.SourceBounds, pageGeometry, parseResult.RenderProfile);
            var relativePath = artifactPaths.GetRelativeCellImagePath(result.GroupKey, result.ColumnKey, result.RollBandLabel);
            var fullPath = artifactPaths.ResolveRelativePath(relativePath);

            await pdfXmlExtractor.RenderCropPngAsync(
                pdfPath,
                crop.PageNumber,
                crop.CropLeft,
                crop.CropTop,
                crop.CropWidth,
                crop.CropHeight,
                fullPath,
                parseResult.RenderProfile.RenderDpi,
                cancellationToken);

            result.SourceImagePath = relativePath;
            result.SourceImageCrop = crop;

            var cellKey = CreateCellKey(result.GroupKey, result.RollBandLabel, result.ColumnKey);
            if (cellsByKey.TryGetValue(cellKey, out var cellArtifact))
            {
                cellArtifact.SourceImagePath = relativePath;
                cellArtifact.SourceImageCrop = crop;
            }
        }
    }

    private static CriticalSourceImageCrop CreateCrop(
        ParsedCriticalSourceRect sourceBounds,
        ParsedPdfPageGeometry pageGeometry,
        SourceRenderProfile renderProfile)
    {
        var cropLeft = Math.Max(0, sourceBounds.Left - CropPaddingX);
        var cropTop = Math.Max(0, sourceBounds.Top - CropPaddingY);
        var cropRight = Math.Min(pageGeometry.Width, sourceBounds.Left + sourceBounds.Width + CropPaddingX);
        var cropBottom = Math.Min(pageGeometry.Height, sourceBounds.Top + sourceBounds.Height + CropPaddingY);

        return new CriticalSourceImageCrop(
            sourceBounds.PageNumber,
            renderProfile.ScaleCoordinate(pageGeometry.Width),
            renderProfile.ScaleCoordinate(pageGeometry.Height),
            renderProfile.ScaleCoordinate(sourceBounds.Left),
            renderProfile.ScaleCoordinate(sourceBounds.Top),
            renderProfile.ScaleCoordinate(sourceBounds.Width),
            renderProfile.ScaleCoordinate(sourceBounds.Height),
            renderProfile.ScaleCoordinate(cropLeft),
            renderProfile.ScaleCoordinate(cropTop),
            renderProfile.ScaleCoordinate(Math.Max(1, cropRight - cropLeft)),
            renderProfile.ScaleCoordinate(Math.Max(1, cropBottom - cropTop)),
            renderProfile.RenderDpi,
            renderProfile.ScaleFactor);
    }

    private static string CreateCellKey(string? groupKey, string rollBandLabel, string columnKey) =>
        $"{groupKey ?? string.Empty}|{rollBandLabel}|{columnKey}";
}
