"use strict";

const test = require("node:test");
const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const workflow = require("../../Web/named-playlist-workflow.js");

const repositoryRoot = path.resolve(__dirname, "..", "..");
const mainWindow = fs.readFileSync(path.join(repositoryRoot, "MainWindow.xaml.cs"), "utf8");
const namedBridge = fs.readFileSync(path.join(repositoryRoot, "MainWindow.NamedPlaylists.cs"), "utf8");
const pagePlanBridge = fs.readFileSync(path.join(repositoryRoot, "MainWindow.PlaylistPagePlans.cs"), "utf8");
const appScript = fs.readFileSync(path.join(repositoryRoot, "Web", "app.js"), "utf8");

test("native switch and JavaScript contract use the same five requests and four responses", () => {
  for (const requestType of Object.values(workflow.bridgeContract.requests)) {
    assert.match(mainWindow, new RegExp(`case "${requestType}"`));
  }
  for (const responseType of Object.values(workflow.bridgeContract.responses)) {
    assert.match(namedBridge, new RegExp(`"${responseType}"`));
  }
  assert.match(mainWindow, /new LegacyNamedPlaylistPersistenceService\(/);
  assert.match(mainWindow, /new OracleNamedPlaylistMutationExecutor\(/);
});

test("named reads use latest-request cancellation and the shared playout-priority database gate", () => {
  assert.match(namedBridge,
    /Interlocked\.Exchange\(\s*ref _namedPlaylistReadCancellation,\s*requestCancellation\)/s);
  assert.match(namedBridge, /CancelRequest\(previousCancellation\)/);
  assert.ok((namedBridge.match(/await _databaseActivityGate\.WaitAsync\(requestToken\)/g) || []).length >= 2);
  assert.ok((namedBridge.match(/Volatile\.Read\(ref _playoutCommandInFlight\) != 0/g) || []).length >= 4);
  assert.match(mainWindow, /CancelRequest\(Volatile\.Read\(ref _namedPlaylistReadCancellation\)\)/);
  assert.match(mainWindow, /CancelRequest\(Volatile\.Read\(ref _namedPlaylistWriteCancellation\)\)/);
});

test("writes are single-flight, never superseded, and permanently quarantine OutcomeUnknown", () => {
  assert.match(namedBridge, /_namedPlaylistWriteGate\.WaitAsync\(0\)/);
  assert.match(namedBridge, /catch \(NamedPlaylistMutationException exception\)/);
  assert.match(namedBridge, /if \(exception\.OutcomeUnknown\)/);
  assert.match(namedBridge,
    /Interlocked\.CompareExchange\(ref _namedPlaylistWriteQuarantined, 1, 0\)/);
  assert.match(namedBridge, /IsNamedPlaylistWriteQuarantined\(\)/);
  assert.match(namedBridge, /do not retry|was not retried/i);
  assert.doesNotMatch(namedBridge, /for\s*\([^)]*retry|while\s*\([^)]*retry/i);
});

test("browser correlation loss cancels active requests and latches an in-flight write", () => {
  assert.match(mainWindow,
    /private void QuarantineIfBrowserCorrelationCanBeLost\(\)\s*\{\s*InvalidateNamedPlaylistBrowserRequests\(\)/s);
  assert.match(namedBridge, /Interlocked\.Increment\(ref _namedPlaylistBrowserGeneration\)/);
  assert.match(namedBridge, /Volatile\.Read\(ref _namedPlaylistWriteInFlight\) != 0/);
  assert.match(namedBridge,
    /Interlocked\.Exchange\(\s*ref _namedPlaylistWriteCancellation,\s*null\)/s);
});

test("native replacement accepts only the exact eight raw-row properties and 1000 rows", () => {
  for (const property of [
    "itemIndex", "enabled", "groupCode", "subject",
    "graphicType", "subtype", "pageText", "dataCode"
  ]) {
    assert.match(namedBridge, new RegExp(`"${property}"`));
  }
  assert.match(namedBridge,
    /itemsElement\.GetArrayLength\(\) > LegacyNamedPlaylistPersistenceService\.MaximumItems/);
  assert.match(namedBridge, /itemIndex != expectedIndex/);
  assert.match(namedBridge, /value\.Contains\('\^'\)/);
  assert.match(namedBridge,
    /return string\.Equals\(page\.ToString\(\), value, StringComparison\.Ordinal\)/);
});

test("page preflight uses one correlated read-only bridge with playout priority", () => {
  assert.match(mainWindow, /case "request-playlist-page-plans"/);
  assert.match(mainWindow, /CancelRequest\(Volatile\.Read\(ref _playlistPagePlanCancellation\)\)/);
  assert.match(pagePlanBridge,
    /Interlocked\.Exchange\(\s*ref _playlistPagePlanCancellation,\s*requestCancellation\)/s);
  assert.match(pagePlanBridge, /await _databaseActivityGate\.WaitAsync\(requestToken\)/);
  assert.ok((pagePlanBridge.match(/Volatile\.Read\(ref _playoutCommandInFlight\) != 0/g) || []).length >= 2);
  assert.match(pagePlanBridge, /PreflightPagePlansAsync\(/);
  assert.match(pagePlanBridge, /PostMessage\("playlist-page-plans-results"/);
  assert.match(pagePlanBridge, /PostMessage\("playlist-page-plans-error"/);
  assert.doesNotMatch(pagePlanBridge, /PrepareAsync\(|PlayAsync\(|ClearAsync\(|UnloadAsync\(/);
});

test("browser page-plan handling requires exact correlation and exposes explicit retry", () => {
  assert.match(appScript, /normalizePagePlanResponse\(payload, pending\.request\)/);
  assert.match(appScript, /payload\?\.requestId !== pending\.requestId/);
  assert.match(appScript, /applyPagePlanResponse\(/);
  assert.match(appScript, /page-result-empty/);
  assert.match(appScript, /namedPlaylistPageRetryButton/);
  assert.match(appScript, /case "playlist-page-plans-results"/);
  assert.match(appScript, /case "playlist-page-plans-error"/);
  assert.doesNotMatch(appScript, /markPageRecalculated\(/);
});
