using System.Globalization;
using System.IO;
using ConeCalorimeter.Models;
using NPOI.HSSF.UserModel;
using NPOI.SS.UserModel;

namespace ConeCalorimeter.Services;

public sealed class NpoiReportExportService : IReportExportService
{
    private const string TemplateFileName = "纯POE-EVA-样12603271445.xls";

    private static readonly string[] DataHeaders =
    [
        "时间(s)",
        "热释放(30)(kW/m²)",
        "产烟率(30)(m²/s)",
        "总热释放 (MJ/m²)",
        "总产烟量(m²)",
        "火焰增长指数(W/㎡·s)",
        "烟气增长指数A(m²/s²)",
        "O2 (%)",
        "CO2 (%)",
        "CO (%)",
        "吸光率%",
        "孔板压差 (Pa)",
        "孔板温度 (K)",
        "MLR (g/s)",
        "热释放KW/m2",
        "EHC",
        "损失质量",
        "Timestamp",
        "点火计时(s)",
        "火焰监测",
        "孔板流量",
        "辐射锥温度 (℃)",
        "当前质量",
        "初始质量",
        "最大热释放",
        "C-系数"
    ];

    public void Export(string outputPath, ReportInput input, IReadOnlyList<RealtimeDataRecord> records)
    {
        var exportRecords = PrepareExportRecords(records);
        if (exportRecords.Count == 0)
        {
            throw new InvalidOperationException("请先点击“测试开始”，产生有效实验数据后再导出报表。");
        }

        if (exportRecords.Any(record => !record.IsHeatReleaseCorrected)
            || exportRecords.Any(record => string.IsNullOrWhiteSpace(record.HeatReleaseCorrectionVersion)))
        {
            throw new InvalidOperationException("正式报表要求使用已完成响应校正的 HRR/THR 数据。");
        }

        ValidateExportRecords(exportRecords);

        var templatePath = FindTemplatePath();
        using var templateStream = File.OpenRead(templatePath);
        var workbook = new HSSFWorkbook(templateStream);

        FillReportSheet(workbook.GetSheet("Result_ISO"), "Result_ISO", input, exportRecords);
        FillReportSheet(workbook.GetSheet("Result_FTP"), "Result_FTP", input, exportRecords);
        FillGraphSheet(workbook.GetSheet("Graph"), input);
        FillBaseSheet(workbook.GetSheet("BASE"), exportRecords);
        FillDataSheet(workbook.GetSheet("Data") ?? workbook.CreateSheet("Data"), exportRecords);
        ValidateWorkbookDataSheet(workbook, exportRecords.Count);

        using var outputStream = File.Create(outputPath);
        workbook.Write(outputStream);
    }

    private static List<RealtimeDataRecord> PrepareExportRecords(IReadOnlyList<RealtimeDataRecord> records)
    {
        return records
            .Where(IsValidExperimentRecord)
            .OrderBy(record => record.TestSeconds)
            .ThenBy(record => record.Timestamp)
            .ToList();
    }

    private static void FillReportSheet(
        ISheet? sheet,
        string sheetName,
        ReportInput input,
        IReadOnlyList<RealtimeDataRecord> records)
    {
        if (sheet is null)
        {
            throw new InvalidOperationException($"报表模板缺少工作表：{sheetName}");
        }

        var summary = BuildSummary(records);
        ValidateSummary(summary);

        SetValueBesideLabel(sheet, "实验室名称", input.LaboratoryName);
        SetValueBesideLabel(sheet, "实验员", input.Operator);
        SetValueBesideLabel(sheet, "文件名", input.FileName);
        SetValueBesideLabel(sheet, "报告名", input.ReportName);
        SetValueBesideLabel(sheet, "样品描述", input.SampleDescription);
        SetValueBesideLabel(sheet, "材料", input.Material);
        SetValueBesideLabel(sheet, "样品", input.SampleName);
        SetValueBesideLabel(sheet, "厚度", input.Thickness);
        SetRequiredValueBesideLabel(sheet, "初始质量", FirstNonEmpty(input.InitialMass, summary.InitialMass));
        SetValueBesideLabel(sheet, "辐射面积", input.IrradiatedArea);
        SetValueBesideLabel(sheet, "热辐射值", input.Irradiance);
        SetValueBesideLabel(sheet, "辐射距离", input.IrradianceDistance);
        SetValueBesideLabel(sheet, "试样方向", input.SampleOrientation);
        SetValueBesideLabel(sheet, "试样数目", input.SampleCount);
        SetValueBesideLabel(sheet, "要求烟气流量", input.RequiredExhaustFlow);
        SetValueBesideLabel(sheet, "边框选用", input.FrameSelected);
        SetValueBesideLabel(sheet, "栅格选用", input.GridSelected);
        SetValueBesideLabel(sheet, "是否选用基材", input.SubstrateSelected);
        SetValueBesideLabel(sheet, "制造商", input.Manufacturer);
        SetValueBesideLabel(sheet, "环境温度", input.AmbientTemperature);
        SetValueBesideLabel(sheet, "环境湿度", input.AmbientHumidity);
        SetValueBesideLabel(sheet, "测试条件", input.TestCondition);
        SetValueBesideLabel(sheet, "符合标准", input.TestStandard);
        SetValueBesideLabel(sheet, "测试标准", input.TestStandard);
        SetValueBesideLabel(sheet, "测试时间", input.TestDate.ToString("yyyy-MM-dd", CultureInfo.CurrentCulture));
        SetValueBesideLabel(sheet, "测试日期", input.TestDate.ToString("yyyy-MM-dd", CultureInfo.CurrentCulture));
        SetValueBesideLabel(sheet, "预测条件", input.ForecastCondition);
        SetValueBesideLabel(sheet, "大气温度", input.AtmosphericTemperature);
        SetValueBesideLabel(sheet, "大气压力", input.AtmosphericPressure);
        SetValueBesideLabel(sheet, "大气湿度", input.AtmosphericHumidity);
        SetValueBesideLabel(sheet, "时间记录", input.TimeRecord);
        SetValueBesideLabel(sheet, "点燃时间", summary.IgnitionTime);
        SetValueBesideLabel(sheet, "熄灭时间", input.FlameoutTime);
        SetValueBesideLabel(sheet, "结束标准", input.EndCriteria);
        SetValueBesideLabel(sheet, "结束时间", summary.EndTime);
        SetValueBesideLabel(sheet, "E等价热值", input.EquivalentHeatValue);
        SetRequiredValueBesideLabel(sheet, "C-系数", FirstNonEmpty(input.CFactor, summary.CFactor));
        SetValueBesideLabel(sheet, "光程", input.LightPath);
        SetValueBesideLabel(sheet, "O2延迟时间", input.O2DelayTime);
        SetValueBesideLabel(sheet, "CO2延迟时间", input.CO2DelayTime);
        SetValueBesideLabel(sheet, "CO延迟时间", input.CODelayTime);
        SetValueBesideLabel(sheet, "OD矫正系数", input.OdCorrectionFactor);
        SetValueBesideLabel(sheet, "基线大气压氧含量", input.BaselineAtmosphericOxygen);
        SetValueBesideLabel(sheet, "基线氧含量", input.BaselineOxygen);
        SetValueBesideLabel(sheet, "基线C2氧含量", input.BaselineC2Oxygen);
        SetValueBesideLabel(sheet, "基线CO2氧含量", input.BaselineCO2Oxygen);

        SetRequiredValueBesideLabel(sheet, "总热释放", summary.TotalHeatRelease);
        SetValueBesideLabel(sheet, "总产烟量", summary.TotalSmoke);
        SetValueBesideLabel(sheet, "质量损失", summary.MassLoss);
        if (!SetValueBesideLabel(sheet, "热释放(30)最大", summary.PeakHeatReleaseRate))
        {
            SetPeakValueInResultMatrix(sheet, "热释放率", summary.PeakHeatReleaseRate);
        }

        if (!SetValueBesideLabel(sheet, "产烟率(30)最大", summary.PeakSmokeProduction))
        {
            SetPeakValueInResultMatrix(sheet, "总产烟率", summary.PeakSmokeProduction);
        }
    }

    private static void FillDataSheet(ISheet sheet, IReadOnlyList<RealtimeDataRecord> records)
    {
        ClearSheet(sheet);

        var fourDecimalCellStyle = sheet.Workbook.CreateCellStyle();
        fourDecimalCellStyle.DataFormat = sheet.Workbook.CreateDataFormat().GetFormat("0.0000");

        var headerRow = sheet.CreateRow(0);
        for (var i = 0; i < DataHeaders.Length; i++)
        {
            headerRow.CreateCell(i).SetCellValue(DataHeaders[i]);
        }

        for (var i = 0; i < records.Count; i++)
        {
            var row = sheet.CreateRow(i + 1);
            var record = records[i];
            var figra = CalculateGrowthIndex(record.TestSeconds, record.HeatReleaseRate, 1000);
            var smogra = CalculateGrowthIndex(record.TestSeconds, record.SmokeProduction, 1);
            // Keep columns A-G fixed; the Excel template charts reference these positions.
            SetNumeric(row, 0, record.TestSeconds >= 0 ? record.TestSeconds : double.NaN);
            SetNumeric(row, 1, record.HeatReleaseRate);
            SetNumeric(row, 2, record.SmokeProduction);
            SetNumeric(row, 3, record.TotalHeatRelease);
            SetNumeric(row, 4, record.TotalSmoke);
            SetNumeric(row, 5, figra);
            SetNumeric(row, 6, smogra);
            SetNumeric(row, 7, record.Oxygen);
            SetNumeric(row, 8, record.CarbonDioxide);
            SetNumeric(row, 9, record.CarbonMonoxide);
            SetNumeric(row, 10, record.Absorbance);
            SetNumeric(row, 11, record.OrificePressure);
            SetNumeric(row, 12, record.OrificeTemperature);
            SetNumeric(row, 13, record.MassLossRate);
            SetNumeric(row, 14, record.HeatReleaseRateKw);
            SetNumeric(row, 15, record.EffectiveHeatOfCombustion);
            SetNumeric(row, 16, record.MassLoss);
            row.CreateCell(17).SetCellValue(record.Timestamp.ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.CurrentCulture));
            SetNumeric(row, 18, record.IgnitionSeconds >= 0 ? record.IgnitionSeconds : double.NaN);
            SetNumeric(row, 19, record.FlameDetected ? 1 : 0);
            SetNumeric(row, 20, record.OrificeFlow);
            SetNumeric(row, 21, record.ConeTemperature);
            SetNumeric(row, 22, record.CurrentMass);
            SetNumeric(row, 23, record.InitialMass);
            SetNumeric(row, 24, record.PeakHeatReleaseRate);
            SetNumeric(row, 25, record.CFactor, fourDecimalCellStyle);
        }

        for (var i = 0; i < DataHeaders.Length; i++)
        {
            sheet.AutoSizeColumn(i);
        }
    }

    private static void FillGraphSheet(ISheet? sheet, ReportInput input)
    {
        if (sheet is null)
        {
            return;
        }

        SetGraphValuesBesideLabel(sheet, "实验室名称", input.LaboratoryName);
        SetGraphValuesBesideLabel(sheet, "实验员", input.Operator);
        SetGraphValuesBesideLabel(sheet, "文件名", input.FileName);
        SetGraphValuesBesideLabel(sheet, "报告名", input.ReportName);
        SetGraphValuesBesideLabel(sheet, "样品描述", input.SampleDescription);
        SetGraphValuesBesideLabel(sheet, "材料", input.Material);
    }

    private static void FillBaseSheet(ISheet? sheet, IReadOnlyList<RealtimeDataRecord> records)
    {
        if (sheet is null)
        {
            return;
        }

        ClearRowsBelowHeader(sheet);

        for (var i = 0; i < records.Count; i++)
        {
            var row = sheet.GetRow(i + 1) ?? sheet.CreateRow(i + 1);
            var record = records[i];

            SetNumeric(row, 0, record.TestSeconds >= 0 ? record.TestSeconds : double.NaN);
            SetNumeric(row, 1, record.HeatReleaseRate);
            SetNumeric(row, 2, record.Oxygen);
            SetNumeric(row, 3, record.CarbonMonoxide);
            SetNumeric(row, 4, record.CarbonDioxide);
            SetNumeric(row, 5, record.CurrentMass);
            SetNumeric(row, 6, record.TotalHeatRelease);
            SetNumeric(row, 7, record.SmokeProduction);
            SetNumeric(row, 8, record.TotalSmoke);
            SetNumeric(row, 13, record.OrificePressure);
        }
    }

    private static double CalculateGrowthIndex(int testSeconds, double value, double multiplier)
    {
        if (testSeconds <= 0 || !double.IsFinite(value) || value < 0)
        {
            return double.NaN;
        }

        return value * multiplier / testSeconds;
    }

    private static void ValidateExportRecords(IReadOnlyList<RealtimeDataRecord> records)
    {
        var previousSeconds = -1;
        foreach (var record in records)
        {
            if (record.TestSeconds < 0)
            {
                throw new InvalidOperationException("导出数据时间列包含负数，无法生成准确曲线。");
            }

            if (record.TestSeconds < previousSeconds)
            {
                throw new InvalidOperationException("导出数据时间列存在倒退，无法生成准确曲线。");
            }

            previousSeconds = record.TestSeconds;
        }

        RequireAnyFiniteCurveValue(records, "热释放", record => record.HeatReleaseRate);
        RequireAnyFiniteCurveValue(records, "产烟率", record => record.SmokeProduction);
        RequireAnyFiniteCurveValue(records, "总热释放", record => record.TotalHeatRelease);
        RequireAnyFiniteCurveValue(records, "总产烟量", record => record.TotalSmoke);
    }

    private static void RequireAnyFiniteCurveValue(
        IReadOnlyList<RealtimeDataRecord> records,
        string label,
        Func<RealtimeDataRecord, double> selector)
    {
        if (!records.Any(record => double.IsFinite(selector(record))))
        {
            throw new InvalidOperationException($"导出数据缺少有效曲线列：{label}。请确认实验数据采集正常。");
        }
    }

    private static void ValidateWorkbookDataSheet(IWorkbook workbook, int expectedDataRows)
    {
        var sheet = workbook.GetSheet("Data")
            ?? throw new InvalidOperationException("导出报表缺少 Data 工作表，曲线无法显示。");

        if (sheet.LastRowNum < expectedDataRows)
        {
            throw new InvalidOperationException("导出报表 Data 工作表行数不足，曲线数据不完整。");
        }

        for (var columnIndex = 0; columnIndex <= 6; columnIndex++)
        {
            var header = sheet.GetRow(0)?.GetCell(columnIndex);
            if (CellText(header) != DataHeaders[columnIndex])
            {
                throw new InvalidOperationException($"导出报表 Data 工作表缺少曲线列：{DataHeaders[columnIndex]}。");
            }

            var hasNumericValue = false;
            for (var rowIndex = 1; rowIndex <= expectedDataRows; rowIndex++)
            {
                var cell = sheet.GetRow(rowIndex)?.GetCell(columnIndex);
                if (cell?.CellType == CellType.Numeric && double.IsFinite(cell.NumericCellValue))
                {
                    hasNumericValue = true;
                    break;
                }
            }

            if (!hasNumericValue)
            {
                throw new InvalidOperationException($"导出报表 Data 工作表曲线列没有有效数值：{DataHeaders[columnIndex]}。");
            }
        }
    }

    private static bool SetValueBesideLabel(ISheet sheet, string label, string value)
    {
        var updated = false;
        for (var rowIndex = sheet.FirstRowNum; rowIndex <= sheet.LastRowNum; rowIndex++)
        {
            var row = sheet.GetRow(rowIndex);
            if (row is null)
            {
                continue;
            }

            for (var columnIndex = row.FirstCellNum; columnIndex < row.LastCellNum; columnIndex++)
            {
                if (columnIndex < 0)
                {
                    continue;
                }

                var cell = row.GetCell(columnIndex);
                if (!IsMatchingLabel(CellText(cell), label))
                {
                    continue;
                }

                var targetColumn = FindTargetColumn(sheet, rowIndex, columnIndex);
                var target = row.GetCell(targetColumn) ?? row.CreateCell(targetColumn);
                SetTextOrBlank(target, value);
                updated = true;
            }
        }

        return updated;
    }

    private static void SetGraphValuesBesideLabel(ISheet sheet, string label, string value)
    {
        for (var rowIndex = sheet.FirstRowNum; rowIndex <= sheet.LastRowNum; rowIndex++)
        {
            var row = sheet.GetRow(rowIndex);
            if (row is null)
            {
                continue;
            }

            for (var columnIndex = row.FirstCellNum; columnIndex < row.LastCellNum; columnIndex++)
            {
                if (columnIndex < 0)
                {
                    continue;
                }

                var cell = row.GetCell(columnIndex);
                if (!IsMatchingLabel(CellText(cell), label))
                {
                    continue;
                }

                var targetColumn = FindTargetColumn(sheet, rowIndex, columnIndex);
                var target = row.GetCell(targetColumn) ?? row.CreateCell(targetColumn);
                SetTextOrBlank(target, value);
            }
        }
    }

    private static bool IsMatchingLabel(string cellText, string label)
    {
        var normalizedCell = NormalizeLabel(cellText);
        var normalizedLabel = NormalizeLabel(label);

        return normalizedCell.Equals(normalizedLabel, StringComparison.CurrentCultureIgnoreCase)
            || normalizedCell.StartsWith($"{normalizedLabel}(", StringComparison.CurrentCultureIgnoreCase)
            || normalizedCell.StartsWith($"{normalizedLabel}（", StringComparison.CurrentCultureIgnoreCase);
    }

    private static string NormalizeLabel(string value)
    {
        var chars = value
            .Where(ch => !char.IsWhiteSpace(ch) && ch is not ':' and not '：' and not '?' and not '？')
            .ToArray();
        return new string(chars);
    }

    private static void SetTextOrBlank(ICell cell, string value)
    {
        cell.SetCellValue(string.IsNullOrWhiteSpace(value) ? string.Empty : value);
    }

    private static void SetRequiredValueBesideLabel(ISheet sheet, string label, string value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new InvalidOperationException($"缺少关键导出数据：{label}");
        }

        if (!SetValueBesideLabel(sheet, label, value))
        {
            throw new InvalidOperationException($"报表模板缺少字段：{label}");
        }
    }

    private static bool SetPeakValueInResultMatrix(ISheet sheet, string rowLabel, string value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            return false;
        }

        for (var rowIndex = sheet.FirstRowNum; rowIndex <= sheet.LastRowNum; rowIndex++)
        {
            var row = sheet.GetRow(rowIndex);
            if (row is null)
            {
                continue;
            }

            for (var columnIndex = row.FirstCellNum; columnIndex < row.LastCellNum; columnIndex++)
            {
                if (columnIndex < 0)
                {
                    continue;
                }

                var cell = row.GetCell(columnIndex);
                if (!CellText(cell).Contains(rowLabel, StringComparison.CurrentCultureIgnoreCase))
                {
                    continue;
                }

                var target = row.GetCell(7) ?? row.CreateCell(7);
                target.SetCellValue(value);
                return true;
            }
        }

        return false;
    }

    private static int FindTargetColumn(ISheet sheet, int rowIndex, int columnIndex)
    {
        for (var i = 0; i < sheet.NumMergedRegions; i++)
        {
            var region = sheet.GetMergedRegion(i);
            if (region.IsInRange(rowIndex, columnIndex))
            {
                return region.LastColumn + 1;
            }
        }

        return columnIndex + 1;
    }

    private static string CellText(ICell? cell)
    {
        if (cell is null)
        {
            return string.Empty;
        }

        return cell.CellType switch
        {
            CellType.String => cell.StringCellValue,
            CellType.Numeric => cell.NumericCellValue.ToString(CultureInfo.CurrentCulture),
            CellType.Boolean => cell.BooleanCellValue.ToString(CultureInfo.CurrentCulture),
            CellType.Formula => cell.ToString() ?? string.Empty,
            _ => cell.ToString() ?? string.Empty
        };
    }

    private static void ClearSheet(ISheet sheet)
    {
        for (var rowIndex = sheet.FirstRowNum; rowIndex <= sheet.LastRowNum; rowIndex++)
        {
            var row = sheet.GetRow(rowIndex);
            if (row is not null)
            {
                sheet.RemoveRow(row);
            }
        }
    }

    private static void ClearRowsBelowHeader(ISheet sheet)
    {
        for (var rowIndex = Math.Max(sheet.FirstRowNum + 1, 1); rowIndex <= sheet.LastRowNum; rowIndex++)
        {
            var row = sheet.GetRow(rowIndex);
            if (row is null)
            {
                continue;
            }

            for (var columnIndex = row.FirstCellNum; columnIndex < row.LastCellNum; columnIndex++)
            {
                var cell = columnIndex >= 0 ? row.GetCell(columnIndex) : null;
                if (cell is not null)
                {
                    row.RemoveCell(cell);
                }
            }
        }
    }

    private static void SetNumeric(IRow row, int columnIndex, double value, ICellStyle? cellStyle = null)
    {
        if (double.IsFinite(value))
        {
            var cell = row.CreateCell(columnIndex);
            cell.SetCellValue(value);
            if (cellStyle is not null)
            {
                cell.CellStyle = cellStyle;
            }
        }
    }

    private static string FindTemplatePath()
    {
        var candidates = new[]
        {
            Path.Combine(AppContext.BaseDirectory, TemplateFileName),
            Path.Combine(Environment.CurrentDirectory, TemplateFileName),
            Path.Combine(AppContext.BaseDirectory, "..", "..", "..", TemplateFileName),
            Path.Combine(AppContext.BaseDirectory, "..", "..", "..", "..", TemplateFileName)
        };

        foreach (var candidate in candidates)
        {
            var fullPath = Path.GetFullPath(candidate);
            if (File.Exists(fullPath))
            {
                return fullPath;
            }
        }

        throw new FileNotFoundException($"未找到报表模板：{TemplateFileName}");
    }

    private static ReportSummary BuildSummary(IReadOnlyList<RealtimeDataRecord> records)
    {
        if (records.Count == 0)
        {
            return new ReportSummary("", "", "", "", "", "", "", "", "");
        }

        var last = records[^1];
        return new ReportSummary(
            PeakHeatReleaseRate: FormatWithUnit(MaxFinite(records, record => record.HeatReleaseRate), "kW/㎡"),
            PeakSmokeProduction: FormatWithUnit(MaxFinite(records, record => record.SmokeProduction), "m²/s"),
            TotalHeatRelease: FormatWithUnit(LastFinite(records, record => record.TotalHeatRelease), "MJ/㎡"),
            TotalSmoke: FormatWithUnit(LastFinite(records, record => record.TotalSmoke), "m²"),
            MassLoss: FormatWithUnit(LastFinite(records, record => record.MassLoss), "g"),
            InitialMass: FormatWithUnit(LastFinite(records, record => record.InitialMass), "g"),
            CFactor: FormatValue(LastFinite(records, record => record.CFactor)),
            IgnitionTime: FormatSeconds(LastNonNegative(records, record => record.IgnitionSeconds)),
            EndTime: FormatSeconds(last.TestSeconds));
    }

    private static void ValidateSummary(ReportSummary summary)
    {
        RequireSummaryValue("总热释放", summary.TotalHeatRelease);
        RequireSummaryValue("总产烟量", summary.TotalSmoke);
        RequireSummaryValue("质量损失", summary.MassLoss);
        RequireSummaryValue("初始质量", summary.InitialMass);
        RequireSummaryValue("C-系数", summary.CFactor);
        RequireSummaryValue("热释放(30)最大", summary.PeakHeatReleaseRate);
        RequireSummaryValue("产烟率(30)最大", summary.PeakSmokeProduction);
    }

    private static void RequireSummaryValue(string label, string value)
    {
        if (string.IsNullOrWhiteSpace(value))
        {
            throw new InvalidOperationException(
                $"缺少关键导出数据：{label}。请确认已点击测试开始并采集到有效实验数据。");
        }
    }

    private static string FormatWithUnit(double? value, string unit)
    {
        return value is null || !double.IsFinite(value.Value) ? "" : $"{value.Value:0.00} {unit}";
    }

    private static string FormatValue(double? value)
    {
        return value is null || !double.IsFinite(value.Value)
            ? ""
            : value.Value.ToString("0.0000", CultureInfo.InvariantCulture);
    }

    private static string FormatSeconds(int? value)
    {
        return value.HasValue && value.Value >= 0 ? $"{value.Value} s" : "";
    }

    private static bool IsValidExperimentRecord(RealtimeDataRecord record)
    {
        return record.TestSeconds >= 0
            && double.IsFinite(record.TotalHeatRelease)
            && double.IsFinite(record.TotalSmoke);
    }

    private static double? LastFinite(IReadOnlyList<RealtimeDataRecord> records, Func<RealtimeDataRecord, double> selector)
    {
        for (var i = records.Count - 1; i >= 0; i--)
        {
            var value = selector(records[i]);
            if (double.IsFinite(value))
            {
                return value;
            }
        }

        return null;
    }

    private static double? MaxFinite(IReadOnlyList<RealtimeDataRecord> records, Func<RealtimeDataRecord, double> selector)
    {
        double? max = null;
        foreach (var record in records)
        {
            var value = selector(record);
            if (double.IsFinite(value) && (!max.HasValue || value > max.Value))
            {
                max = value;
            }
        }

        return max;
    }

    private static int? LastNonNegative(IReadOnlyList<RealtimeDataRecord> records, Func<RealtimeDataRecord, int> selector)
    {
        for (var i = records.Count - 1; i >= 0; i--)
        {
            var value = selector(records[i]);
            if (value >= 0)
            {
                return value;
            }
        }

        return null;
    }

    private static string FirstNonEmpty(params string[] values)
    {
        return values.FirstOrDefault(value => !string.IsNullOrWhiteSpace(value)) ?? string.Empty;
    }

    private sealed record ReportSummary(
        string PeakHeatReleaseRate,
        string PeakSmokeProduction,
        string TotalHeatRelease,
        string TotalSmoke,
        string MassLoss,
        string InitialMass,
        string CFactor,
        string IgnitionTime,
        string EndTime);
}
