@page "/sick"
@implements IDisposable

@inject IOptions<CanBusOptions> CanOptions
@inject ICanBusService CanService
@inject IJSRuntime JS


<div class="encoder-container">
    <div class="encoder-card">
        <!-- HEADER -->
        <div class="encoder-header">
            <div class="encoder-title">Sick Encoder Monitor</div>

            <div class="encoder-actions">
                <button class="btn btn-start @( _starting ? "loading" : "" )"
                        disabled="@(_starting || _applying)"
                        @onclick="OnStartClicked">
                    @(_starting ? "Starting..." : "Start")
                </button>

                <button class="btn btn-stop @( _stopping ? "loading" : "" )"
                        disabled="@(_stopping || _applying)"
                        @onclick="OnStopClicked">
                    @(_stopping ? "Stopping..." : "Stop")
                </button>
            </div>


        </div>

        <!-- TABLE -->
        <table class="encoder-table">
            <thead>
                <tr>
                    <th>CAN-ID</th>
                    <th>Length</th>
                    <th>Data</th>
                    <th>Position (mm)</th>
                    <th>Time</th>
                </tr>
            </thead>

            <tbody>
                @foreach (var f in _frames.Values.OrderBy(f => f.CobId))
                {
                    <tr>
                        <td class="encoder-id">@($"0x{f.CobId:X3}")</td>
                        <td>@f.Length</td>
                        <td class="encoder-data">@f.DataHex</td>
                        <td class="encoder-pos">
                            @(f.PositionMm.HasValue? f.PositionMm.Value.ToString("F2") : "-")
                        </td>
                        <td class="encoder-time">@f.Timestamp.ToString("HH:mm:ss.fff")</td>
                    </tr>
                }
            </tbody>


        </table>

        <!-- STATUS -->
        <div class="encoder-status @StateClass">  Node State: @_nodeState | Bitrate: @(CurrentBitrate / 1000) kbit/s | Node ID: @CurrentNodeId</div>
        <div class="bitrate-group">
            <label class="bitrate-label">Bitrate</label>

            <select @bind="_selectedBitrate" class="bitrate-select">
                <option value="1000000">1000 kbit/s</option>
                <option value="800000">800 kbit/s</option>
                <option value="500000">500 kbit/s</option>
                <option value="250000">250 kbit/s</option>
                <option value="125000">125 kbit/s</option>
                <option value="100000">100 kbit/s</option>
                <option value="50000">50 kbit/s</option>
                <option value="20000">20 kbit/s</option>
            </select>

            <button class="btn btn-apply @( _applying ? "loading" : "" )"
                    disabled="@(_starting || _applying)"
                    @onclick="OnApplyClicked">
                @(_applying ? "Applying..." : "Apply Bitrate")
            </button>
            <label class="nodeid-label">Node ID (1–127)</label>

            <input type="number"
                   min="1"
                   max="127"
                   @bind="_inputNodeId"
                   placeholder="@CurrentNodeId"
                   class="nodeid-input" />

            <button class="btn btn-apply"
                    disabled="@(!_inputNodeId.HasValue)"
                    @onclick="OnApplyNodeIdClicked">
                Apply Node ID
            </button>
        </div>
    </div>
</div>

@code {
    private PositionPdo? _current;
    private CanNodeState _nodeState = CanNodeState.Unknown;
    private int CurrentBitrate => CanService.CurrentBitrate;
    private readonly Dictionary<uint, CanFrame> _frames = new();
    private int _selectedBitrate;
    private bool _bitrateApplied = false;
    private bool _starting;
    private bool _stopping;
    private bool _applying;
    private int? _inputNodeId;
    private byte CurrentNodeId => CanService.CurrentNodeId;

    protected override void OnInitialized()
    {
        CanService.PositionReceived += OnPositionReceived;
        CanService.NodeStateChanged += OnNodeStateChanged;
        CanService.FrameReceived += OnFrameReceived;
        CanService.NodeIdChanged += OnNodeIdChanged;

        _selectedBitrate = CanService.CurrentBitrate;
    }

    private async Task Start()
    {
        // ❗ LUÔN init lại để đảm bảo COB-ID & state sạch
        await CanService.InitAsync();

        // ❗ Reset node theo Node ID MỚI
        CanService.SendNmtReset(CurrentNodeId);
        await Task.Delay(800);

        CanService.SendNmtStart(CurrentNodeId);

        // ❗ Start read với Node ID MỚI
        CanService.Start();
    }

    private Task Stop()
    {
        CanService.SendNmtStop(CurrentNodeId);
        return Task.CompletedTask;
    }

    private void OnPositionReceived(object? sender, PositionPdo e)
    {
        _ = InvokeAsync(() =>
        {
            _current = e;
            StateHasChanged();
        });
    }

    public void Dispose()
    {
        CanService.PositionReceived -= OnPositionReceived;
        CanService.NodeStateChanged -= OnNodeStateChanged;
        CanService.FrameReceived -= OnFrameReceived;
        CanService.NodeIdChanged -= OnNodeIdChanged;
        CanService.Stop();
    }

    private void OnNodeStateChanged(object? sender, CanNodeState state)
    {
        _ = InvokeAsync(() =>
        {
            _nodeState = state;
            StateHasChanged();
        });
    }

    private string StateClass => _nodeState switch
    {
        CanNodeState.Operational => "state-op",
        CanNodeState.PreOperational => "state-preop",
        CanNodeState.Stopped => "state-stop",
        CanNodeState.Timeout => "state-timeout",
        _ => "state-unknown"
    };

    private void OnFrameReceived(object? sender, CanFrame frame)
    {
        _ = InvokeAsync(() =>
        {
            // mỗi CAN-ID chỉ giữ frame mới nhất
            _frames[frame.CobId] = frame;
            StateHasChanged();
        });
    }

    private async Task ApplyBitrate()
    {
        await CanService.ApplyBitrateAsync(
            CurrentNodeId,
            _selectedBitrate
        );

        // UI chỉ báo PreOp / Stopped
        _nodeState = CanNodeState.PreOperational;
    }

    private async Task OnApplyNodeIdClicked()
    {
        if (!_inputNodeId.HasValue) return;

        _applying = true; // Sử dụng flag để disable các nút khác
        StateHasChanged();

        try
        {
            byte newNodeId = (byte)_inputNodeId.Value;
            byte oldNodeId = CanService.CurrentNodeId;

            // 1. Gửi lệnh đổi ID qua CAN bus
            await CanService.ApplyNodeIdAsync(oldNodeId, newNodeId);

            // 2. DỪNG SERVICE NGAY LẬP TỨC
            // Điều này ngăn việc nhận frame từ ID cũ
            CanService.Stop();

            // 3. Reset UI state
            _frames.Clear();
            _nodeState = CanNodeState.PreOperational; 
            _inputNodeId = null;
        }
        catch (Exception ex)
        {
            // Log lỗi nếu cần
        }
        finally
        {
            _applying = false;
            StateHasChanged();
        }
    }

    private async Task OnStartClicked()
    {
        if (_starting) return;

        _starting = true;
        StateHasChanged();

        try
        {
            // ✅ Nếu CHƯA Apply bitrate → dùng bitrate hiện tại
            if (!_bitrateApplied)
            {
                _selectedBitrate = CanService.CurrentBitrate;
            }

            await Start();
        }
        finally
        {
            _starting = false;
            StateHasChanged();
        }
    }

    private async Task OnStopClicked()
    {
        if (_stopping) return;

        _stopping = true;
        StateHasChanged();

        try
        {
            await Stop();
        }
        finally
        {
            _stopping = false;
            StateHasChanged();
        }
    }

    private async Task OnApplyClicked()
    {
        if (_applying) return;

        _applying = true;
        StateHasChanged();

        try
        {
            await ApplyBitrate();

            // ✅ Đánh dấu đã Apply
            _bitrateApplied = true;
        }
        finally
        {
            _applying = false;
            StateHasChanged();
        }
    }

    private void OnNodeIdChanged(object? sender, byte newNodeId)
    {
        _ = InvokeAsync(() =>
        {
            // 🔥 XÓA TOÀN BỘ FRAME CŨ
            _frames.Clear();

            // reset input
            _inputNodeId = null;

            StateHasChanged();
        });
    }


}