using DeepDrftModels.DTOs;
using DeepDrftModels.Enums;
using Models.Common;
using NetBlocks.Models;
namespace DeepDrftManager.Services;
///
/// CMS-side track operations for the Manager host. Every read and write goes over HTTP to the
/// DeepDrftAPI API, which is the single authority over both the SQL metadata store and the
/// binary audio vault. DeepDrftManager holds no in-process data layer.
///
public interface ICmsTrackService
{
///
/// Proxy a WAV upload to DeepDrftAPI. The Content API owns the dual-database write and
/// returns the persisted track carrying the SQL-assigned Id. A vault-without-SQL
/// orphan is handled and logged server-side; here it surfaces as a failed result.
/// is the browser's filename, captured at upload time and
/// stored as metadata; it is not user-editable afterwards.
/// sets the parent release's when this upload
/// creates the release. The medium is authoritative only on creation — adding a track to an existing
/// release never changes its medium (that is the edit path, ).
/// is the total payload size (the browser file's Size); it
/// sets Content-Length and is the denominator for , which reports cumulative
/// bytes pushed to the wire. Each progress tick also resets the idle/heartbeat upload timeout, so a
/// stalled connection aborts without a fixed total-duration cap.
/// distinguishes the two rows of a within-batch multi-track Cut: null on
/// the first row (CREATE — the server rejects a pre-existing (title, artist) as a duplicate) and the
/// id returned by that first row on rows 2..N (ATTACH — the server skips the duplicate check and adds
/// the track to the release the batch just created).
///
Task> UploadTrackAsync(
Stream wavStream,
long contentLength,
string fileName,
string contentType,
string trackName,
string artist,
string? album,
string? genre,
string? description,
string? releaseDate,
string? originalFileName,
long createdByUserId,
ReleaseType releaseType,
int trackNumber,
ReleaseMedium medium = ReleaseMedium.Cut,
long? releaseId = null,
IProgress? progress = null,
CancellationToken ct = default);
///
/// Upload-form pre-flight: returns the existing release whose exact (title, artist) matches, or null
/// when none exists. Backs the duplicate block the form runs BEFORE transferring bytes, so the admin
/// is not surprised at the end of a long upload. A 404 from the API is the not-found (null) case, not
/// a failure. The match semantics are the API's GetReleaseByTitleAndArtist — the same read the
/// server backstop uses — so the pre-flight and the backstop agree.
///
Task> GetExistingReleaseAsync(
string title, string artist, CancellationToken ct = default);
///
/// Delete a track via the Content API, which removes the SQL row then the vault entry.
/// Maps a 404 to a "Track not found." failure.
///
Task DeleteTrackAsync(long id, CancellationToken ct = default);
///
/// Replace an existing track's audio via POST api/track/{id}/replace-audio. Swaps only the
/// vault bytes and regenerates the track's waveform data server-side; the track id, vault key,
/// release membership, position, and metadata are preserved. Uses the dedicated upload client and
/// the same two-phase (idle / response-wait) cancellation as , since
/// a WAV replace is a large-body upload. sets Content-Length and is
/// the denominator for ; each progress tick resets the idle heartbeat.
/// Maps a 404 to a "Track not found." failure.
///
Task ReplaceTrackAudioAsync(
long id,
Stream wavStream,
long contentLength,
string fileName,
string contentType,
IProgress? progress = null,
CancellationToken ct = default);
///
/// Soft-delete a release record via DELETE api/track/release/{id}. Use when a release
/// has no live tracks and needs to be removed from the albums browser.
///
Task DeleteReleaseAsync(long releaseId, CancellationToken ct = default);
///
/// Fetch a page of track metadata from the Content API's GET api/track/page. Optional
/// and filters narrow the result to a single
/// release title or genre; null leaves the dimension unfiltered.
///
Task>> GetPagedAsync(
int page, int pageSize, string? sortColumn, bool sortDescending,
string? album = null, string? genre = null,
CancellationToken ct = default);
///
/// Fetch a single track's metadata from GET api/track/meta/{id}. A 404 returns a
/// passing result with a null value.
///
Task> GetByIdAsync(long id, CancellationToken ct = default);
///
/// Upload a cover-art image to the images vault via POST api/image/upload.
/// Returns the generated entry key on success. Maps a 400 to a validation failure message.
///
Task> UploadImageAsync(
Stream imageStream,
string fileName,
string contentType,
CancellationToken ct = default);
///
/// Update a track's metadata via PUT api/track/meta/{id}. EntryKey is immutable and
/// not part of the update. is tri-state: null leaves the
/// cover art unchanged, "" clears it, and any other value sets it.
/// is null = no change; a non-null, non-Cut value resets the release's ReleaseType to its default
/// server-side, since ReleaseType is meaningful only for Cut.
///
Task UpdateAsync(
long id, string trackName, string artist,
string? album, string? genre, string? description, DateOnly? releaseDate,
string? imagePath = null,
ReleaseType? releaseType = null,
ReleaseMedium? medium = null,
int? trackNumber = null,
CancellationToken ct = default);
///
/// Fetch per-track waveform profile status from GET api/track/waveform-status for the
/// CMS PreProcessing panel. Unpaged — the admin catalogue is small.
///
Task> GetWaveformStatusAsync(CancellationToken ct = default);
///
/// Trigger waveform profile generation for a single track via
/// POST api/track/{entryKey}/waveform. Maps a 404 to a "Track audio not found." failure.
///
Task GenerateWaveformProfileAsync(string entryKey, CancellationToken ct = default);
///
/// Trigger high-res visualizer datum generation for a single track via
/// POST api/track/{entryKey}/waveform/high-res (phase-12 §5). Re-runnable — recomputes on each
/// call. Drives the per-row generate action and the batch backfill. Maps a 404 to a "Track audio not
/// found." failure.
///
Task GenerateHighResWaveformAsync(string entryKey, CancellationToken ct = default);
///
/// Trigger the catalogue-wide Backfill-Opus pass via POST api/track/opus/backfill (Phase 18.5).
/// The API enqueues a background Opus derive for every track lacking a complete Opus artifact and returns
/// the (enqueued, skipped) counts. Enqueue-only — the transcodes run server-side on a serial background
/// worker, so this call returns as soon as the work is scheduled, not when transcoding finishes. The
/// Enqueued count is how many derives were scheduled; Skipped is how many already had Opus.
///
Task> BackfillOpusAsync(CancellationToken ct = default);
/// Returns all releases with track counts from GET api/track/albums.
Task>> GetReleasesAsync(CancellationToken ct = default);
///
/// Returns the total track count by calling GET api/track/page with pageSize=1 and reading TotalCount.
///
Task> GetTrackCountAsync(CancellationToken ct = default);
}
///
/// Outcome of a Backfill-Opus pass (Phase 18.5): how many tracks had a background derive scheduled
/// () and how many were skipped because they already carry a complete Opus
/// artifact (). Both are counts of tracks, not finished transcodes — the work
/// runs asynchronously on the API's background worker after this returns.
///
public readonly record struct OpusBackfillResult(int Enqueued, int Skipped);