@page "/clusters/{ClusterId}/equipment/import"
@* Bulk equipment import via pasted CSV. Header row required; columns:
       Name, MachineCode, UnsLineId, DriverInstanceId, ZTag, SAPID, Manufacturer, Model
   Empty optional columns parsed as null. EquipmentId is system-generated per row
   (matches single-add path in EquipmentEdit.razor). *@
@attribute [Microsoft.AspNetCore.Authorization.Authorize]
@rendermode RenderMode.InteractiveServer
@using Microsoft.AspNetCore.Components.Forms
@using Microsoft.EntityFrameworkCore
@using ZB.MOM.WW.OtOpcUa.Configuration
@using ZB.MOM.WW.OtOpcUa.Configuration.Entities
@inject IDbContextFactory<OtOpcUaConfigDbContext> DbFactory
@inject NavigationManager Nav

<div class="d-flex justify-content-between align-items-center mb-3">
    <h4 class="mb-0">Import equipment &middot; <span class="mono">@ClusterId</span></h4>
    <a href="/clusters/@ClusterId/equipment" class="btn btn-outline-secondary btn-sm">Cancel</a>
</div>

<ClusterNav ClusterId="@ClusterId" ActiveTab="equipment" />

<section class="panel notice rise" style="animation-delay:.02s">
    Paste CSV below. Required header columns (in order):
    <span class="mono">Name, MachineCode, UnsLineId, DriverInstanceId</span>.
    Optional: <span class="mono">ZTag, SAPID, Manufacturer, Model</span>.
    Each row inserts one Equipment with a freshly-generated EquipmentId. Existing rows are
    detected by MachineCode and skipped (the importer is additive-only — no updates).
</section>

<section class="panel rise mt-3" style="animation-delay:.08s">
    <div class="panel-head">CSV</div>
    <div style="padding:1rem">
        <textarea class="form-control form-control-sm mono" rows="14"
                  @bind="_csv" @bind:event="oninput"
                  placeholder="Name,MachineCode,UnsLineId,DriverInstanceId,ZTag,SAPID,Manufacturer,Model&#10;mixer-01,MX_001,line-3,drv-modbus-line3-01,ZT-12345,SAP-9876,Siemens,SIMATIC-1500"></textarea>
    </div>
</section>

@if (!string.IsNullOrWhiteSpace(_error))
{
    <div class="panel notice mt-3" style="border-color:var(--alert)">@_error</div>
}

@if (_preview is not null)
{
    <section class="panel rise mt-3" style="animation-delay:.14s">
        <div class="panel-head">Preview &middot; @_preview.Count row@(_preview.Count == 1 ? "" : "s") to import</div>
        @if (_preview.Count > 0)
        {
            <div class="table-wrap">
                <table class="data-table">
                    <thead>
                        <tr>
                            <th>Name</th>
                            <th>MachineCode</th>
                            <th>UNS line</th>
                            <th>Driver</th>
                            <th>ZTag</th>
                            <th>SAPID</th>
                            <th>Manufacturer</th>
                            <th>Model</th>
                            <th>Status</th>
                        </tr>
                    </thead>
                    <tbody>
                        @foreach (var p in _preview)
                        {
                            <tr>
                                <td>@p.Name</td>
                                <td><span class="mono">@p.MachineCode</span></td>
                                <td><span class="mono small">@p.UnsLineId</span></td>
                                <td><span class="mono small">@p.DriverInstanceId</span></td>
                                <td>@(p.ZTag ?? "")</td>
                                <td>@(p.SAPID ?? "")</td>
                                <td>@(p.Manufacturer ?? "")</td>
                                <td>@(p.Model ?? "")</td>
                                <td>
                                    @if (p.IsSkipped) { <span class="chip chip-idle">skip — exists</span> }
                                    else if (!string.IsNullOrEmpty(p.RowError)) { <span class="chip chip-alert">@p.RowError</span> }
                                    else { <span class="chip chip-ok">ready</span> }
                                </td>
                            </tr>
                        }
                    </tbody>
                </table>
            </div>
        }
    </section>
}

<div class="mt-3 d-flex gap-2">
    <button class="btn btn-outline-primary" @onclick="PreviewAsync" disabled="@_busy">
        @if (_busy) { <span class="spinner-border spinner-border-sm me-1"></span> }
        Preview
    </button>
    <button class="btn btn-primary" @onclick="ImportAsync"
            disabled="@(_busy || _preview is null || _preview.All(p => p.IsSkipped || !string.IsNullOrEmpty(p.RowError)))">
        Import @(_preview?.Count(p => !p.IsSkipped && string.IsNullOrEmpty(p.RowError)) ?? 0) row(s)
    </button>
    <a href="/clusters/@ClusterId/equipment" class="btn btn-outline-secondary">Cancel</a>
</div>

@code {
    [Parameter] public string ClusterId { get; set; } = "";

    private string _csv = "";
    private List<PreviewRow>? _preview;
    private bool _busy;
    private string? _error;

    private static readonly string[] RequiredColumns = ["Name", "MachineCode", "UnsLineId", "DriverInstanceId"];
    private static readonly string[] OptionalColumns = ["ZTag", "SAPID", "Manufacturer", "Model"];

    private async Task PreviewAsync()
    {
        _busy = true;
        _error = null;
        _preview = null;
        try
        {
            var parsed = ParseCsv(_csv);
            if (parsed is null) return;

            await using var db = await DbFactory.CreateDbContextAsync();
            var driversInCluster = await db.DriverInstances.AsNoTracking()
                .Where(d => d.ClusterId == ClusterId)
                .Select(d => d.DriverInstanceId)
                .ToListAsync();
            var driverSet = driversInCluster.ToHashSet(StringComparer.Ordinal);
            var areaIds = await db.UnsAreas.AsNoTracking()
                .Where(a => a.ClusterId == ClusterId)
                .Select(a => a.UnsAreaId).ToListAsync();
            var validLines = await db.UnsLines.AsNoTracking()
                .Where(l => areaIds.Contains(l.UnsAreaId))
                .Select(l => l.UnsLineId).ToListAsync();
            var lineSet = validLines.ToHashSet(StringComparer.Ordinal);
            var existingMachineCodes = await db.Equipment.AsNoTracking()
                .Select(e => e.MachineCode).ToListAsync();
            var existingSet = existingMachineCodes.ToHashSet(StringComparer.OrdinalIgnoreCase);

            foreach (var row in parsed)
            {
                if (existingSet.Contains(row.MachineCode))
                {
                    row.IsSkipped = true;
                    continue;
                }
                if (!driverSet.Contains(row.DriverInstanceId))
                {
                    row.RowError = $"driver '{row.DriverInstanceId}' not in this cluster";
                    continue;
                }
                if (!lineSet.Contains(row.UnsLineId))
                {
                    row.RowError = $"UNS line '{row.UnsLineId}' not in this cluster";
                }
            }
            _preview = parsed;
        }
        catch (Exception ex) { _error = ex.Message; }
        finally { _busy = false; }
    }

    private async Task ImportAsync()
    {
        if (_preview is null) return;
        _busy = true;
        _error = null;
        try
        {
            await using var db = await DbFactory.CreateDbContextAsync();
            var added = 0;
            foreach (var row in _preview.Where(p => !p.IsSkipped && string.IsNullOrEmpty(p.RowError)))
            {
                var uuid = Guid.NewGuid();
                var equipmentId = $"EQ-{uuid.ToString("N")[..12]}";
                db.Equipment.Add(new Equipment
                {
                    EquipmentId = equipmentId,
                    EquipmentUuid = uuid,
                    DriverInstanceId = row.DriverInstanceId,
                    UnsLineId = row.UnsLineId,
                    Name = row.Name,
                    MachineCode = row.MachineCode,
                    ZTag = row.ZTag,
                    SAPID = row.SAPID,
                    Manufacturer = row.Manufacturer,
                    Model = row.Model,
                    Enabled = true,
                });
                added++;
            }
            await db.SaveChangesAsync();
            Nav.NavigateTo($"/clusters/{ClusterId}/equipment");
        }
        catch (Exception ex) { _error = ex.Message; }
        finally { _busy = false; }
    }

    private List<PreviewRow>? ParseCsv(string csv)
    {
        if (string.IsNullOrWhiteSpace(csv)) { _error = "CSV is empty."; return null; }
        var lines = csv.Replace("\r\n", "\n").Split('\n', StringSplitOptions.RemoveEmptyEntries);
        if (lines.Length < 2) { _error = "Need a header row and at least one data row."; return null; }

        var header = lines[0].Split(',').Select(c => c.Trim()).ToArray();
        for (var i = 0; i < RequiredColumns.Length; i++)
        {
            if (i >= header.Length || !string.Equals(header[i], RequiredColumns[i], StringComparison.OrdinalIgnoreCase))
            {
                _error = $"Header column #{i + 1} must be '{RequiredColumns[i]}' (got '{(i < header.Length ? header[i] : "")}').";
                return null;
            }
        }

        var rows = new List<PreviewRow>();
        for (var lineIdx = 1; lineIdx < lines.Length; lineIdx++)
        {
            var parts = lines[lineIdx].Split(',').Select(c => c.Trim()).ToArray();
            if (parts.Length < RequiredColumns.Length)
            {
                rows.Add(new PreviewRow { RowError = $"too few columns (got {parts.Length}, need {RequiredColumns.Length})" });
                continue;
            }
            rows.Add(new PreviewRow
            {
                Name = parts[0],
                MachineCode = parts[1],
                UnsLineId = parts[2],
                DriverInstanceId = parts[3],
                ZTag = NullIfEmpty(parts, 4),
                SAPID = NullIfEmpty(parts, 5),
                Manufacturer = NullIfEmpty(parts, 6),
                Model = NullIfEmpty(parts, 7),
            });
        }
        return rows;
    }

    private static string? NullIfEmpty(string[] parts, int idx) =>
        idx < parts.Length && !string.IsNullOrWhiteSpace(parts[idx]) ? parts[idx] : null;

    private sealed class PreviewRow
    {
        public string Name { get; set; } = "";
        public string MachineCode { get; set; } = "";
        public string UnsLineId { get; set; } = "";
        public string DriverInstanceId { get; set; } = "";
        public string? ZTag { get; set; }
        public string? SAPID { get; set; }
        public string? Manufacturer { get; set; }
        public string? Model { get; set; }
        public bool IsSkipped { get; set; }
        public string? RowError { get; set; }
    }
}
