@page "/relay-drum"
@rendermode InteractiveServer
@implements IDisposable

@using IOModuleTestBlazor.Services
@using Peak.Can.Basic

@inject ICanService CanService

<PageTitle>Relay Drum Machine</PageTitle>

<div class="container-fluid">
    <h1 class="mb-3">Relay Drum Machine</h1>

    @if (!CanService.IsConnected)
    {
        <div class="alert alert-warning mb-3">CAN not connected — connect on the Home page first.</div>
    }

    @* ── Transport ───────────────────────────────────────────────────────────── *@
    <div class="card mb-3">
        <div class="card-body">
            <div class="d-flex align-items-center gap-4 flex-wrap">
                <button class="btn @(_playing ? "btn-danger" : "btn-success") btn-lg px-4"
                        @onclick="TogglePlay"
                        disabled="@(!CanService.IsConnected)">
                    @(_playing ? "⏹ Stop" : "▶ Play")
                </button>

                <div class="d-flex align-items-center gap-2">
                    <label class="fw-semibold mb-0">BPM</label>
                    <input type="range" class="form-range" style="width:160px"
                           min="40" max="240" step="1"
                           @bind="_bpm" @bind:event="oninput" />
                    <span class="font-monospace fw-bold" style="min-width:3ch">@_bpm</span>
                </div>

                <div class="d-flex align-items-center gap-2">
                    <label class="fw-semibold mb-0">Pulse</label>
                    <input type="range" class="form-range" style="width:120px"
                           min="20" max="80" step="5"
                           @bind="_pulseDurationMs" @bind:event="oninput" />
                    <span class="font-monospace" style="min-width:5ch">@_pulseDurationMs ms</span>
                </div>

                <button class="btn btn-outline-secondary" @onclick="ClearPattern">Clear</button>
            </div>
        </div>
    </div>

    @* ── Presets ─────────────────────────────────────────────────────────────── *@
    <div class="card mb-3">
        <div class="card-header fw-semibold">Presets</div>
        <div class="card-body d-flex gap-2 flex-wrap">
            @foreach (var preset in _presets)
            {
                <button class="btn btn-outline-primary btn-sm" @onclick="() => LoadPreset(preset.Value)">
                    @preset.Key
                </button>
            }
        </div>
    </div>

    @* ── Pattern grid ────────────────────────────────────────────────────────── *@
    <div class="card mb-3">
        <div class="card-header d-flex align-items-center justify-content-between">
            <span class="fw-semibold">Pattern — @_barCount bar@(_barCount != 1 ? "s" : "") (@StepCount steps, 4/4 16th-note grid)</span>
            <div class="d-flex align-items-center gap-2">
                <button class="btn btn-outline-secondary btn-sm"
                        @onclick="RemoveBar" disabled="@(_barCount <= 1)">− Bar</button>
                <span class="font-monospace px-1">@_barCount</span>
                <button class="btn btn-outline-secondary btn-sm"
                        @onclick="AddBar" disabled="@(_barCount >= 8)">+ Bar</button>
            </div>
        </div>
        <div class="card-body">

            @* Beat header — shown once above the first bar *@
            <div class="beat-header">
                <div class="bar-label-col"></div>
                <div class="step-grid-col beat-header-grid">
                    @for (int b = 0; b < 4; b++)
                    {
                        <div class="beat-header-cell">@(b + 1)</div>
                    }
                </div>
            </div>

            @* One row per bar *@
            @for (int bar = 0; bar < _barCount; bar++)
            {
                var barIndex = bar;
                <div class="bar-row @(barIndex == _currentBar ? "bar-row-active" : "")">
                    <div class="bar-label-col">
                        <span class="bar-label">B@(barIndex + 1)</span>
                    </div>
                    <div class="step-grid-col">
                        <div class="step-grid">
                            @for (int s = 0; s < 16; s++)
                            {
                                var globalStep = barIndex * 16 + s;
                                var localStep = s;
                                bool isGroupStart = localStep % 4 == 0;
                                <div class="step-cell @GetStepClass(globalStep) @(isGroupStart ? "beat-start" : "")"
                                     @onclick="() => ToggleStep(globalStep)">
                                    <span class="step-num">@(localStep + 1)</span>
                                </div>
                            }
                        </div>
                    </div>
                </div>
            }
        </div>
    </div>

    @* ── Status bar ──────────────────────────────────────────────────────────── *@
    <div class="card">
        <div class="card-body py-2 d-flex align-items-center gap-3">
            @if (_playing && _currentStep >= 0)
            {
                <span class="badge bg-success">● Playing</span>
                <span class="font-monospace">Bar @(_currentBar + 1) · Beat @(_currentBeat + 1) · Step @(_currentStepInBar + 1)</span>
                <span class="text-muted">(global step @(_currentStep + 1) / @StepCount)</span>
            }
            else
            {
                <span class="badge bg-secondary">■ Stopped</span>
            }
            <span class="text-muted ms-auto">@(_steps.Count(s => s)) / @StepCount steps active</span>
        </div>
    </div>
</div>

<style>
    .beat-header {
        display: flex;
        align-items: center;
        gap: 8px;
        margin-bottom: 4px;
    }

    .beat-header-grid {
        display: grid;
        grid-template-columns: repeat(4, 1fr);
    }

    .beat-header-cell {
        text-align: center;
        font-size: 0.7rem;
        font-weight: bold;
        color: #6c757d;
        padding-left: 4px;
    }

    .bar-label-col {
        min-width: 36px;
        flex-shrink: 0;
    }

    .bar-label {
        font-size: 0.7rem;
        font-weight: bold;
        color: #6c757d;
    }

    .step-grid-col {
        flex: 1;
        min-width: 0;
    }

    .bar-row {
        display: flex;
        align-items: center;
        gap: 8px;
        margin-bottom: 6px;
        border-radius: 6px;
        padding: 2px 4px;
        transition: background-color 0.1s;
    }

    .bar-row-active {
        background-color: rgba(255, 193, 7, 0.08);
    }

    .step-grid {
        display: grid;
        grid-template-columns: repeat(16, 1fr);
        gap: 4px;
    }

    .step-cell {
        aspect-ratio: 1;
        border: 2px solid #495057;
        border-radius: 5px;
        cursor: pointer;
        display: flex;
        align-items: center;
        justify-content: center;
        transition: background-color 0.1s, box-shadow 0.1s;
        user-select: none;
        min-width: 0;
    }

    .step-cell:hover { border-color: #0d6efd; }

    .step-num {
        font-size: 0.6rem;
        font-weight: bold;
        line-height: 1;
    }

    .beat-start { border-left: 2px solid #6c757d; }

    /* States */
    .step-off       { background-color: #343a40; color: #6c757d; }
    .step-on        { background-color: #0d6efd; border-color: #0d6efd; color: white; }
    .step-cursor    { box-shadow: 0 0 0 2px #ffc107; border-color: #ffc107 !important; }
    .step-cursor-on { background-color: #fd7e14; border-color: #ffc107 !important; color: white;
                      box-shadow: 0 0 10px rgba(253,126,20,0.8), 0 0 0 2px #ffc107; }
</style>

@code {
    private const int StepsPerBar = 16;

    private int _barCount = 1;
    private int StepCount => _barCount * StepsPerBar;

    private bool[] _steps = new bool[StepsPerBar];
    private int _currentStep = -1;
    private int _currentBar  => _currentStep < 0 ? -1 : _currentStep / StepsPerBar;
    private int _currentStepInBar => _currentStep < 0 ? -1 : _currentStep % StepsPerBar;
    private int _currentBeat => _currentStep < 0 ? -1 : (_currentStep % StepsPerBar) / 4;

    private bool _playing;
    private int _bpm = 120;
    private int _pulseDurationMs = 30;

    private CancellationTokenSource? _cts;

    private static readonly Dictionary<string, bool[]> _presets = new()
    {
        ["Four-on-Floor"] = [true, false, false, false, true, false, false, false, true, false, false, false, true, false, false, false],
        ["Kick 1+3"]      = [true, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false],
        ["Snare 2+4"]     = [false, false, false, false, true, false, false, false, false, false, false, false, true, false, false, false],
        ["Hi-Hat 8th"]    = [true, false, true, false, true, false, true, false, true, false, true, false, true, false, true, false],
        ["Hi-Hat 16th"]   = [true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true],
        ["Off-Beat"]      = [false, false, true, false, false, false, true, false, false, false, true, false, false, false, true, false],
        ["Clave 3-2"]     = [true, false, false, true, false, false, true, false, false, false, true, false, false, true, false, false],
    };

    private string GetStepClass(int step)
    {
        bool isCurrent = step == _currentStep;
        bool isOn = _steps[step];
        return (isCurrent, isOn) switch
        {
            (true, true)  => "step-cursor-on",
            (true, false) => "step-off step-cursor",
            (false, true) => "step-on",
            _             => "step-off",
        };
    }

    private void ToggleStep(int step) => _steps[step] = !_steps[step];

    private void ClearPattern()
    {
        Array.Clear(_steps);
        StateHasChanged();
    }

    private void LoadPreset(bool[] pattern)
    {
        // Tile the 16-step preset across however many bars are active
        var next = new bool[StepCount];
        for (int i = 0; i < StepCount; i++)
            next[i] = pattern[i % StepsPerBar];
        _steps = next;
        StateHasChanged();
    }

    private async Task AddBar()
    {
        await StopSequencer();
        Array.Resize(ref _steps, (_barCount + 1) * StepsPerBar);
        _barCount++;
    }

    private async Task RemoveBar()
    {
        if (_barCount <= 1) return;
        await StopSequencer();
        _barCount--;
        Array.Resize(ref _steps, _barCount * StepsPerBar);
    }

    private async Task TogglePlay()
    {
        if (_playing)
            await StopSequencer();
        else
            StartSequencer();
    }

    private void StartSequencer()
    {
        _cts?.Dispose();
        _cts = new CancellationTokenSource();
        _playing = true;
        var steps = _steps;  // snapshot so resize mid-play doesn't affect the running loop
        _ = Task.Run(() => RunSequencer(steps, _cts.Token));
    }

    private async Task StopSequencer()
    {
        _cts?.Cancel();
        // Yield briefly to let the background task react before we return
        await Task.Delay(50);
    }

    private async Task RunSequencer(bool[] steps, CancellationToken ct)
    {
        int total = steps.Length;
        try
        {
            while (!ct.IsCancellationRequested)
            {
                for (int step = 0; step < total && !ct.IsCancellationRequested; step++)
                {
                    _currentStep = step;
                    try { await InvokeAsync(StateHasChanged); } catch { return; }

                    int stepMs  = Math.Max(30, (int)(60_000.0 / _bpm / 4));
                    int pulseMs = Math.Min(_pulseDurationMs, stepMs / 2);

                    if (steps[step])
                    {
                        SendRelay(true);
                        await Task.Delay(pulseMs, ct);
                        SendRelay(false);
                        int rest = stepMs - pulseMs;
                        if (rest > 0) await Task.Delay(rest, ct);
                    }
                    else
                    {
                        await Task.Delay(stepMs, ct);
                    }
                }
            }
        }
        catch (OperationCanceledException) { }
        finally
        {
            _currentStep = -1;
            _playing = false;
            SendRelay(false);
            try { await InvokeAsync(StateHasChanged); } catch { }
        }
    }

    private void SendRelay(bool on)
    {
        var data = new byte[8];
        data[0] = on ? (byte)0x01 : (byte)0x02;  // 0x01=TERM_ON(SET), 0x02=TERM_OFF(RESET)
        CanService.Write(new PcanMessage(0x240, MessageType.Standard, 8, data, false));
    }

    public void Dispose()
    {
        _cts?.Cancel();
        _cts?.Dispose();
    }
}
