@using Radzen
@using Radzen.Blazor
@using Radzen.Blazor.TrimTest.Models

@*
    Faithful end-to-end trimming repro (TRIMMING_AUDIT.md). Models come from a separate <IsTrimmable>
    assembly and are bound to the real components by STRING Property / ValueProperty / CategoryProperty -
    never via @context.X templates, which would root the getters and mask the bug.

    The #trim-status element is the single signal the Playwright harness reads:
      running       -> drive did not complete (treated as failure on timeout)
      done          -> dynamic-LINQ over the model succeeded (getters preserved)
      error:<msg>   -> reflection over the trimmed model threw (the hazard)

    RED today: no DAM on the component generics, so the model getters are trimmed and the OrderBy/Where
    drive throws -> error:... GREEN once the library annotates the item-typed component generics; rooting
    RadzenDataGrid<Order> then preserves Order's members app-wide and the drive succeeds -> done.
*@

<div id="trim-status">@status</div>

<ErrorBoundary>
    <ChildContent>
        <RadzenDataGrid TItem="Order" Data="@orders" AllowSorting="true" AllowFiltering="true" AllowGrouping="true">
            <Columns>
                <RadzenDataGridColumn TItem="Order" Property="CustomerName" Title="Customer" />
                <RadzenDataGridColumn TItem="Order" Property="Total" Title="Total" />
                <RadzenDataGridColumn TItem="Order" Property="Status" Title="Status" />
                <RadzenDataGridColumn TItem="Order" Property="Priority" Title="Priority" />   @* nullable enum (L5) *@
                @* NOT gated: a nested path like Property="Customer.City" fails under trimming because DAM
                   is not transitive - rooting nested model types is the consumer's responsibility (audit
                   H14), not something the library can fix. Like AOT, we don't make a blocking gate out of
                   what the library cannot guarantee. *@
            </Columns>
        </RadzenDataGrid>

        @* Trim-safe DropDown usage (value list). NOT gated: binding by TextProperty over an item type
           that lives in a trimmable assembly is consumer-responsibility (audit H2) - the item type is
           erased through Data=IEnumerable, so it is not the generic parameter and DAM cannot root it. *@
        <RadzenDropDown TValue="string" Data="@(new[] { "A", "B", "C" })" @bind-Value="@selectedText" AllowFiltering="true" />

        @* NOT gated: chart series bind by CategoryProperty/ValueProperty over their TItem and need DAM on
           the series generics (CartesianSeries<TItem> + 33 derived classes). That cascade is a deferred,
           fixable follow-up (audit chart-series decision) - tracked separately, not blocking this gate. *@

        <RadzenScheduler TItem="Appt" Data="@appts" StartProperty="Start" EndProperty="End" TextProperty="Text">
            <RadzenMonthView />
        </RadzenScheduler>

        <RadzenUpload Url="upload" Multiple="true" />
    </ChildContent>
    <ErrorContent Context="ex">
        <div>@SetError(ex)</div>
    </ErrorContent>
</ErrorBoundary>

@code {
    string status = "running";
    string? selectedText;

    Order[] orders =
    [
        new() { CustomerName = "Alice", Total = 42, Status = OrderStatus.New,        Customer = new() { City = "Sofia" } },
        new() { CustomerName = "Bob",   Total = 87, Status = OrderStatus.Shipped,    Customer = new() { City = "Varna" } },
        new() { CustomerName = "Carol", Total = 15, Status = OrderStatus.Processing, Customer = new() { City = "Sofia" } },
    ];
    Appt[] appts = [new() { Start = new DateTime(2026, 1, 1), End = new DateTime(2026, 1, 2), Text = "Kickoff" }];

    protected override Task OnAfterRenderAsync(bool firstRender)
    {
        if (!firstRender) return Task.CompletedTask;

        // Drive the grid's REAL in-memory filter/group engine over the trimmed model (not the raw
        // dynamic-LINQ string-predicate parser, which is inherently trim-unsafe by design and not what
        // the grid uses for in-memory data). Where(filters) -> GetExpression -> typeof(string).GetMethod;
        // GroupByMany -> reflection over T. ErrorBoundary does NOT catch OnAfterRenderAsync, so catch here.
        try
        {
            var filters = new List<FilterDescriptor>
            {
                new() { Property = "CustomerName", FilterValue = "A", FilterOperator = FilterOperator.Contains },
                new() { Property = "Total", FilterValue = 10.0, FilterOperator = FilterOperator.GreaterThan },
            };
            _ = orders.AsQueryable()
                .Where(filters, LogicalFilterOperator.And, FilterCaseSensitivity.CaseInsensitive)
                .ToList();
            _ = orders.AsQueryable().GroupByMany(new[] { "Status" }).ToList();
            status = "done";
        }
        catch (Exception ex)
        {
            // Surface the inner exception too - DynamicExtensions/QueryableExtension wrap the real cause.
            var root = ex.GetBaseException();
            status = $"error:{root.GetType().Name}: {root.Message}";
        }

        StateHasChanged();
        return Task.CompletedTask;
    }

    string SetError(Exception ex)
    {
        // Render-time failure sink (e.g. a cell getter throwing).
        status = $"error:{ex.GetType().Name}: {ex.Message}";
        return status;
    }
}
