namespace RolemasterDb.ImportTool.Parsing;

public sealed class StandardCriticalTableParser
{
    internal CriticalTableParseResult Parse(CriticalImportManifestEntry entry, ExtractedCriticalSource source, StandardTableLayout? layout = null)
    {
        var fragments = source.Fragments;
        var pageGeometries = source.PageGeometries;
        var validationErrors = new List<string>();
        var validationWarnings = new List<string>();

        layout ??= BuildLayout(fragments, validationErrors);
        validationWarnings.AddRange(layout.Warnings);

        var affixLegend = CriticalTableParserSupport.ParseAffixLegend(fragments, layout.KeyTop);
        var affixLegendSymbols = affixLegend.ClassificationSymbols;
        var bodyFragments = CriticalTableParserSupport.BuildBodyFragments(
            fragments,
            layout.BodyStartTop,
            layout.KeyTop,
            layout.LeftCutoff,
            layout.RowAnchors,
            layout.ExcludedFragments,
            layout.ColumnCenters,
            affixLegendSymbols);
        var bodyLines = CriticalTableParserSupport.BuildBodyLines(bodyFragments, layout.ColumnCenters, affixLegendSymbols);

        var parsedRollBands = layout.RowAnchors
            .Select(anchor => CriticalTableParserSupport.CreateRollBand(anchor.Label, anchor.SortOrder))
            .ToList();

        var cellEntries = new List<ColumnarCellEntry>();

        for (var rowIndex = 0; rowIndex < layout.RowAnchors.Count; rowIndex++)
        {
            var rowStart = rowIndex == 0
                ? layout.BodyStartTop
                : CriticalTableParserSupport.ResolveRowBoundaryTop(layout.RowAnchors[rowIndex - 1], layout.RowAnchors[rowIndex], bodyLines);

            var rowEnd = rowIndex == layout.RowAnchors.Count - 1
                ? layout.KeyTop - 1
                : CriticalTableParserSupport.ResolveRowBoundaryTop(layout.RowAnchors[rowIndex], layout.RowAnchors[rowIndex + 1], bodyLines);

            var rowFragments = bodyFragments
                .Where(item => item.Top >= rowStart && item.Top < rowEnd)
                .ToList();

            foreach (var columnAnchor in layout.ColumnCenters)
            {
                var cellFragments = rowFragments
                    .Where(item => CriticalTableParserSupport.ResolveColumn(item.CenterX, layout.ColumnCenters) == columnAnchor.Key)
                    .OrderBy(item => item.Top)
                    .ThenBy(item => item.Left)
                    .ToList();

                if (cellFragments.Count == 0)
                {
                    validationErrors.Add($"Missing content for roll band '{layout.RowAnchors[rowIndex].Label}', column '{columnAnchor.Key}'.");
                    continue;
                }

                cellEntries.Add(new ColumnarCellEntry(
                    null,
                    layout.RowAnchors[rowIndex].Label,
                    rowIndex,
                    columnAnchor.Key,
                    CriticalTableParserSupport.BuildLines(cellFragments).ToList()));
            }
        }

        CriticalTableParserSupport.RepairLeadingAffixLeakage(cellEntries, affixLegendSymbols);

        var parsedCells = new List<ParsedCriticalCellArtifact>();
        var parsedResults = new List<ParsedCriticalResult>();
        CriticalTableParserSupport.BuildParsedArtifacts(
            cellEntries,
            affixLegend,
            parsedCells,
            parsedResults,
            validationErrors,
            validationWarnings,
            downgradeCellContentValidationToWarnings: string.Equals(source.ExtractionMethod, "ocr", StringComparison.OrdinalIgnoreCase));

        if (layout.ColumnCenters.Count != 5)
        {
            validationErrors.Add($"Expected 5 standard-table columns but found {layout.ColumnCenters.Count}.");
        }

        if (parsedCells.Count != layout.RowAnchors.Count * layout.ColumnCenters.Count)
        {
            validationErrors.Add(
                $"Expected {layout.RowAnchors.Count * layout.ColumnCenters.Count} parsed cells but produced {parsedCells.Count}.");
        }

        var validationReport = new ImportValidationReport(
            validationErrors.Count == 0,
            validationErrors,
            validationWarnings,
            layout.RowAnchors.Count,
            parsedCells.Count);

        var table = new ParsedCriticalTable(
            entry.Slug,
            entry.DisplayName,
            entry.Family,
            Path.GetFileName(entry.PdfPath),
            source.ImportNotes,
            [],
            layout.ColumnCenters.Select((item, index) => new ParsedCriticalColumn(item.Key, item.Key, "severity", index + 1)).ToList(),
            parsedRollBands,
            parsedResults);

        return new CriticalTableParseResult(table, pageGeometries, fragments, source.RenderProfile, parsedCells, validationReport);
    }

    public CriticalTableParseResult Parse(CriticalImportManifestEntry entry, string xmlContent)
    {
        return Parse(
            entry,
            new ExtractedCriticalSource(
                "xml",
                "Imported from PDF XML extraction.",
                SourceRenderProfile.XmlAligned(),
                CriticalTableParserSupport.LoadPageGeometries(xmlContent),
                CriticalTableParserSupport.LoadFragments(xmlContent)));
    }

    private static StandardTableLayout BuildLayout(
        IReadOnlyList<PositionedTextFragment> fragments,
        ICollection<string> validationErrors)
    {
        var headerFragments = FindHeaderFragments(fragments);
        var columnCenters = headerFragments
            .OrderBy(item => item.Left)
            .Select(item => (Key: item.Text.ToUpperInvariant(), CenterX: item.CenterX))
            .ToList();
        var headerTop = headerFragments.Max(item => item.Top);
        var keyTop = CriticalTableParserSupport.FindKeyTop(fragments);
        var leftCutoff = headerFragments.Min(item => item.Left) - 10;
        var rowLabelFragments = CriticalTableParserSupport.FindRowLabelFragments(
            fragments,
            leftCutoff,
            headerTop + CriticalTableParserSupport.HeaderToRowLabelMinimumGap,
            keyTop);
        var rowAnchors = CriticalTableParserSupport.CreateRowAnchors(rowLabelFragments);
        var bodyStartTop = CriticalTableParserSupport.ResolveBodyStartTop(headerTop, rowAnchors);

        if (rowAnchors.Count == 0)
        {
            validationErrors.Add("No roll-band labels were found in the source artifact.");
        }

        return new StandardTableLayout(headerFragments, columnCenters, rowAnchors, headerTop, bodyStartTop, keyTop, leftCutoff);
    }

    private static List<PositionedTextFragment> FindHeaderFragments(IReadOnlyList<PositionedTextFragment> fragments)
    {
        var headerCandidates = fragments
            .Where(item => item.Text.Length == 1 && char.IsLetter(item.Text[0]))
            .OrderBy(item => item.Top)
            .ThenBy(item => item.Left)
            .ToList();

        foreach (var group in CriticalTableParserSupport.GroupByTop(headerCandidates))
        {
            var ordered = group.OrderBy(item => item.Left).ToList();
            var labels = ordered.Select(item => item.Text.ToUpperInvariant()).ToList();
            if (labels.SequenceEqual(["A", "B", "C", "D", "E"]))
            {
                return ordered;
            }
        }

        throw new InvalidOperationException("Could not find the standard-table A-E header row in the source artifact.");
    }
}
