"use strict";

const assert = require("node:assert/strict");
const fs = require("node:fs");
const path = require("node:path");
const test = require("node:test");

const manualFinancialUiModule = require("../../Web/manual-financial-ui.js");
const manualFinancialWorkflow = require("../../Web/manual-financial-workflow.js");
const manualListsUiModule = require("../../Web/manual-lists-ui.js");
const manualListsWorkflow = require("../../Web/manual-lists-workflow.js");

const repositoryRoot = path.resolve(__dirname, "..", "..");
const read = relativePath => fs.readFileSync(path.join(repositoryRoot, relativePath), "utf8");
const appHost = read("App.xaml.cs");
const app = read("Web/app.js");
const index = read("Web/index.html");
const styles = read("Web/styles.css");
const manualFinancialUi = read("Web/manual-financial-ui.js");
const mainWindow = read("MainWindow.xaml.cs");
const closeConfirmationBridge = read("MainWindow.CloseConfirmation.cs");
const manualFinancialBridge = read("MainWindow.ManualFinancial.cs");
const manualListsBridge = read("MainWindow.ManualLists.cs");
const namedPlaylistBridge = read("MainWindow.NamedPlaylists.cs");
const operatorCatalogBridge = read("MainWindow.OperatorCatalogs.cs");
const playoutBridge = read("MainWindow.Playout.cs");
const operatorMutationGate = read("src/MBN_STOCK_WEBVIEW.Core/Playout/OperatorMutationGate.cs");

function functionBody(source, name, nextName) {
  const start = source.indexOf(`function ${name}(`);
  assert.ok(start >= 0, `${name} must exist`);
  const end = nextName ? source.indexOf(`function ${nextName}(`, start + 1) : source.length;
  assert.ok(end > start, `${name} must precede ${nextName || "the end of the file"}`);
  return source.slice(start, end);
}

function sliceBetween(source, startMarker, endMarker) {
  const start = source.indexOf(startMarker);
  const end = source.indexOf(endMarker, start + startMarker.length);
  assert.ok(start >= 0, `${startMarker} must exist`);
  assert.ok(end > start, `${endMarker} must follow ${startMarker}`);
  return source.slice(start, end);
}

function assertOrdered(source, values) {
  let previous = -1;
  for (const value of values) {
    const offset = source.indexOf(value);
    assert.ok(offset >= 0, `${value} must be loaded`);
    assert.ok(offset > previous, `${value} must preserve dependency order`);
    assert.equal(source.indexOf(value, offset + value.length), -1, `${value} must be loaded once`);
    previous = offset;
  }
}

test("operator styles and scripts load once in dependency order before app.js", () => {
  assertOrdered(index, [
    'href="manual-lists-ui.css"',
    'href="manual-financial-ui.css"',
    'href="operator-catalog-ui.css"'
  ]);
  assertOrdered(index, [
    'src="manual-lists-workflow.js"',
    'src="manual-financial-workflow.js"',
    'src="operator-catalog-workflow.js"',
    'src="manual-lists-ui.js"',
    'src="manual-financial-ui.js"',
    'src="operator-catalog-ui.js"',
    'src="app.js"'
  ]);
});

test("the packaged app registers one primary instance before constructing any runtime", () => {
  const register = appHost.indexOf("AppInstance.FindOrRegisterForKey(MainInstanceKey)");
  const currentCheck = appHost.indexOf("if (!registeredInstance.IsCurrent)", register);
  const redirect = appHost.indexOf("RedirectActivationToAsync(activation)", currentCheck);
  const secondaryExit = appHost.indexOf("Exit();", redirect);
  const windowConstruction = appHost.indexOf("_window = new MainWindow();", secondaryExit);

  assert.ok(register >= 0);
  assert.ok(currentCheck > register);
  assert.ok(redirect > currentCheck);
  assert.ok(secondaryExit > redirect);
  assert.ok(windowConstruction > secondaryExit);
  assert.match(appHost, /_mainInstance\.Activated \+= OnMainInstanceActivated/u);
  assert.match(appHost,
    /OnMainInstanceActivated\([\s\S]*DispatcherQueue\.TryEnqueue\(window\.Activate\)/u);
  assert.doesNotMatch(appHost, /GetProcessesByName|Process\.Kill|Environment\.Exit/u);
});

test("legacy GraphE, FSell, VIList, ThemeA and EList controls open their real controllers", () => {
  assert.match(index, /id="themeCatalogManageButton"/u);
  assert.match(index, /id="expertCatalogManageButton"/u);

  const fixedManual = functionBody(app, "openFixedManualAction", "addFixedAction");
  assert.match(fixedManual,
    /action\.builderKey === "s5025"[\s\S]*manualListsUi\?\.openNetSell\(action\.selection\?\.groupCode\)/u);
  assert.match(fixedManual,
    /action\.builderKey === "s5074"[\s\S]*action\.selection\?\.groupCode === "PAGED_VI"[\s\S]*manualListsUi\?\.openVi\(\)/u);

  const stockRender = functionBody(app, "renderStockWorkflow", "requestStockSearch");
  assert.match(stockRender, /for \(const action of operatorWorkflow\.manualStockActions\)/u);
  assert.match(stockRender, /button\.disabled = !search\.selected \|\| operatorUiLocked\(\)/u);
  assert.match(stockRender, /manualFinancialUi\?\.open\(action\)/u);

  assert.match(app,
    /themeCatalogManageButton\.addEventListener\("click", \(\) => \{[\s\S]*operatorCatalogUi\?\.openTheme\(context \|\| undefined\)/u);
  assert.match(app,
    /expertCatalogManageButton\.addEventListener\("click", \(\) => \{[\s\S]*operatorCatalogUi\?\.openExpert\(context \|\| undefined\)/u);
  assert.match(app, /themeCatalogManageButton\.disabled = operatorUiLocked\(\)/u);
  assert.match(app, /expertCatalogManageButton\.disabled = operatorUiLocked\(\)/u);
});

test("the original stock search and 31-cut surface stays independent of every business tab", () => {
  const stockPanelStart = index.indexOf('<section class="panel stock-panel">');
  const stockWorkflow = index.indexOf('id="stockWorkflow"', stockPanelStart);
  const catalogPanelStart = index.indexOf('<section class="panel catalog-panel">', stockWorkflow);
  assert.ok(stockPanelStart >= 0);
  assert.ok(stockWorkflow > stockPanelStart);
  assert.ok(catalogPanelStart > stockWorkflow);
  assert.match(styles,
    /\.console-grid\s*\{[^}]*grid-template-columns:\s*minmax\([^;]*minmax\([^;]*minmax\([^;]*minmax\(/u);
  assert.match(styles,
    /\.stock-panel \.stock-workflow\s*\{[^}]*flex:\s*1 1 auto/u);

  const render = functionBody(app, "renderStockWorkflow", "requestStockSearch");
  assert.match(render, /elements\.stockWorkflow\.hidden = false/u);
  assert.doesNotMatch(render, /state\.activeMarket/u);

  const binding = functionBody(app, "bindEvents", null);
  assert.doesNotMatch(binding,
    /industryWorkflow\.markets\.includes\(state\.activeMarket\)[\s\S]{0,160}state\.stockWorkflowCollapsed = true/u);
});

test("the original first overseas tab is the one and only initial market-data view", () => {
  assert.match(index,
    /<button class="nav-item active" data-market="overseas"><span>◉<\/span>해외<\/button>/u);
  assert.match(index,
    /<button class="nav-item" data-market="dashboard"><span>⌂<\/span>대시보드<\/button>/u);
  assert.match(index, /<h1 id="workspaceTitle">해외 그래픽<\/h1>/u);
  assert.match(app, /const state = \{\s*activeMarket: "overseas",/u);

  const initialize = functionBody(app, "initialize", null);
  assert.equal([...initialize.matchAll(/requestMarketData\(/gu)].length, 1);
  assert.match(initialize, /requestMarketData\(state\.activeMarket\);/u);
  assert.doesNotMatch(initialize, /requestMarketData\("(?:index|dashboard|foreign-stock)"\)/u);

  const request = functionBody(app, "requestMarketData", "handleMarketData");
  assert.match(request,
    /postNative\("request-market-data", \{ requestId, view, maximumRowsPerTable: 250 \}\)/u);
});

test("the Web message allowlist has no unused external-open or reload command surface", () => {
  assert.doesNotMatch(mainWindow, /case "open-external"/u);
  assert.doesNotMatch(mainWindow, /case "reload"/u);
  assert.doesNotMatch(app, /postNative\("open-external"/u);
  assert.doesNotMatch(app, /postNative\("reload"/u);
});

test("first-routing consumes only messages owned by an operator controller", () => {
  const nativeHandler = functionBody(app, "handleNativeMessage", "bindEvents");
  const financialOffset = nativeHandler.indexOf("manualFinancialUi?.handleMessage");
  const listsOffset = nativeHandler.indexOf("manualListsUi?.handleMessage");
  const catalogOffset = nativeHandler.indexOf("operatorCatalogUi?.handleMessage");
  const switchOffset = nativeHandler.indexOf("switch (message.type)");
  assert.ok(financialOffset >= 0 && financialOffset < listsOffset);
  assert.ok(listsOffset < catalogOffset && catalogOffset < switchOffset);

  let id = 0;
  const financial = manualFinancialUiModule.createController({
    postNative() {},
    appendPlaylistEntry() { return true; },
    isLocked() { return false; },
    getSelectedStock() { return null; },
    showToast() {},
    addLog() {},
    createId() { id += 1; return `test-${id}`; }
  });
  const unownedFinancialTypes = [
    manualFinancialWorkflow.bridgeContract.listResultType,
    manualFinancialWorkflow.bridgeContract.listErrorType,
    manualFinancialWorkflow.bridgeContract.loadResultType,
    manualFinancialWorkflow.bridgeContract.loadErrorType,
    "stock-search-results",
    "stock-search-error",
    manualFinancialWorkflow.bridgeContract.selectionResultType,
    manualFinancialWorkflow.bridgeContract.selectionErrorType,
    manualFinancialWorkflow.bridgeContract.mutationResultType,
    manualFinancialWorkflow.bridgeContract.mutationErrorType,
    manualFinancialWorkflow.bridgeContract.requestErrorType
  ];
  for (const type of unownedFinancialTypes) {
    assert.equal(financial.handleMessage(type, { requestId: "not-owned" }), false, type);
  }

  const lists = manualListsUiModule.createController({
    postNative() {},
    appendPlaylistEntry() { return true; },
    isLocked() { return false; },
    showToast() {},
    addLog() {},
    createId() { id += 1; return `test-${id}`; }
  });
  for (const type of [
    manualListsWorkflow.bridgeContract.statusResultType,
    manualListsWorkflow.bridgeContract.netSellDataType,
    manualListsWorkflow.bridgeContract.netSellSaveResultType,
    manualListsWorkflow.bridgeContract.viDataType,
    manualListsWorkflow.bridgeContract.viSaveResultType,
    manualListsWorkflow.bridgeContract.errorType,
    "stock-search-results",
    "stock-search-error"
  ]) {
    assert.equal(lists.handleMessage(type, { requestId: "not-owned" }), false, type);
  }
});

test("operator locks and trusted append checks cover every migrated manual surface", () => {
  const lock = functionBody(app, "operatorUiLocked", "selectedStockForManualFinancial");
  assert.match(lock, /isPlaylistSnapshotLocked\(\)/u);
  assert.match(lock, /namedPlaylistBusy\(\)/u);
  assert.match(lock, /state\.manualFinancialRestore\.entries\.size > 0/u);

  const renderPlayout = functionBody(app, "renderPlayout", "clearPlayoutError");
  for (const controller of ["manualListsUi", "manualFinancialUi", "operatorCatalogUi"]) {
    assert.match(renderPlayout, new RegExp(`${controller}\\?\\.setLocked\\(operatorLocked\\)`));
  }

  const append = functionBody(app, "appendTrustedOperatorEntry", "isPlaylistSnapshotLocked");
  assert.match(append, /if \(operatorUiLocked\(\)\) throw/u);
  assert.match(append, /manualListsWorkflow\.restorePlaylistEntry\(entry\)/u);
  assert.match(append, /manualListsWorkflow\.isPlaylistEntryPlayable\(entry\)/u);
  assert.match(append, /manualFinancialWorkflow\.isPlaylistEntryPlayable\(entry\)/u);
  assert.match(append,
    /catalog\.find\(value => value\.builderKey === entry\.builderKey &&[\s\S]*sceneAliases\(value\)\.includes/u);
  assert.match(append, /state\.playlist\.push\(entry\)/u);
  assert.match(manualFinancialUi,
    /if \(!workflow\.isPlaylistEntryPlayable\(entry\)\) throw new TypeError\("Untrusted entry\."\)/u);
});

test("named PLAY_LIST replacement admits manual rows only through live fresh-read trust", () => {
  const trusted = functionBody(
    app,
    "isTrustedNamedPlaylistEntry",
    "selectedNamedPlaylistDefinition");
  assert.match(trusted,
    /namedManualRestoreWorkflow\.isTrustedEntryForSave\(entry, itemIndex\)/u);
  assert.match(trusted,
    /guard && \(!guard\.trusted \|\| \(guard\.pageRecalculationRequired && !guard\.pageRecalculated\)\)/u);

  const projection = functionBody(
    app,
    "namedLegacyFieldsForEntry",
    "isTrustedNamedPlaylistEntry");
  assert.match(projection, /namedManualRestoreWorkflow\.legacyFieldsForEntry\(entry, itemIndex\)/u);
  assert.match(projection, /legacyNamedRowWorkflow\.legacySelectionForEntry\(entry\)/u);
  assert.match(projection, /resolveNamedPlaylistRawRow\(/u);
  assert.match(projection, /if \(!selection\)[\s\S]*throw new Error/u);

  const replace = functionBody(
    app,
    "requestNamedPlaylistReplace",
    "requestNamedPlaylistDelete");
  assert.match(replace, /isTrustedEntry: isTrustedNamedPlaylistEntry/u);
  assert.match(replace, /pageTextForEntry: namedPageTextForEntry/u);
  assert.match(replace, /fieldsForEntry: namedLegacyFieldsForEntry/u);
  assert.match(replace, /beginNamedPlaylistOperation\("replace", request/u);
});

test("local FSell and VI restores remain blocked until a correlated fresh read", () => {
  const normalizeStored = functionBody(
    app,
    "normalizeStoredPlaylist",
    "manualFinancialRestoreRecordForRequest");
  assert.match(normalizeStored, /manualListsWorkflow\.restorePlaylistEntry\(item\)/u);
  assert.match(normalizeStored,
    /namedManualRestoreWorkflow\.createStoredManualListPending\([\s\S]*restoredManualListEntry,[\s\S]*itemIndex/u);
  assert.match(normalizeStored,
    /options\.manualFinancialRestores\.push\(pendingManualListEntry\)/u);

  const install = functionBody(
    app,
    "installManualFinancialRestores",
    "handleManualFinancialRestoreLoadResults");
  assert.match(install, /isStoredManualListPending\(pending\)[\s\S]*"stored-list"/u);

  const handler = functionBody(
    app,
    "handleNamedManualListResults",
    "handleNamedManualListError");
  assert.match(handler, /\["named-list", "stored-list"\]\.includes\(record\.restoreKind\)/u);
  assert.match(handler,
    /record\.restoreKind === "named-list"[\s\S]*trustNamedManualRestoration[\s\S]*matchesMaterializedEntry\(record\.pending, current\)/u);

  const blocker = functionBody(app, "manualFinancialPrepareBlocked", "loadPlaylist");
  assert.match(blocker,
    /legacy-manual-lists-workflow[\s\S]*manualListsWorkflow\.isPlaylistEntryPlayable\(item\)/u);
});

test("named DB operations stay locked for the whole asynchronous manual restore", () => {
  const busy = functionBody(app, "namedPlaylistBusy", "renderNamedPlaylist");
  assert.match(busy, /state\.manualFinancialRestore\.entries\.size > 0/u);

  const render = functionBody(app, "renderNamedPlaylist", "beginNamedPlaylistOperation");
  assert.match(render, /const freshRestorePending = state\.manualFinancialRestore\.entries\.size > 0/u);
  assert.match(render, /const busy = Boolean\(pending\) \|\| freshRestorePending/u);
  assert.match(render, /else if \(freshRestorePending\)[\s\S]*재검증 중/u);
  assert.match(render, /namedPlaylistLoadButton\.disabled = !nativeBridge \|\| locked \|\| busy/u);

  for (const name of [
    "requestNamedPlaylistLoad",
    "requestNamedPlaylistCreate",
    "requestNamedPlaylistReplace",
    "requestNamedPlaylistDelete"
  ]) {
    const body = functionBody(app, name, null);
    assert.match(body, /namedPlaylistBusy\(\)/u);
  }
});

test("playlist editing cannot orphan a queued manual restore and failed rows remain removable", () => {
  const editable = functionBody(app, "requirePlaylistEditable", "updateClock");
  assert.match(editable, /restoreRecords[\s\S]*record\.status === "error"/u);
  assert.match(editable,
    /state\.namedPlaylist\.pending \|\| state\.namedPlaylist\.pagePlanPending/u);
  assert.match(editable, /allowFailedManualRestore && failedRestoreOnly/u);

  const remove = functionBody(app, "deleteSelected", "deleteAllPlaylistEntries");
  assert.match(remove, /deletingFailedRestores/u);
  assert.match(remove, /requirePlaylistEditable\(deletingFailedRestores\)/u);
  assert.match(remove, /requestNextManualFinancialRestore\(\)/u);

  const removeAll = functionBody(app, "deleteAllPlaylistEntries", "selectPlaylistBoundary");
  assert.match(removeAll, /requirePlaylistEditable\(failedRestoreOnly\)/u);
  assert.match(removeAll, /clearNamedPlaylistLoadState\(\)/u);
});

test("stored GraphE entries remain unplayable until exact non-truncated DB revalidation", () => {
  const normalizeStored = functionBody(app, "normalizeStoredPlaylist", "manualFinancialRestoreRecordForRequest");
  assert.match(normalizeStored, /manualFinancialWorkflow\.restorePlaylistEntry\(item\)/u);
  assert.match(normalizeStored, /options\.manualFinancialRestores\.push\(pendingManualFinancialEntry\)/u);
  assert.match(normalizeStored,
    /operatorInputBuilderKeys\.has\(String\(item\.builderKey \|\| ""\)\) \|\| item\.operator !== undefined[\s\S]*return \[\]/u);

  const stockRestore = functionBody(
    app,
    "handleManualFinancialRestoreStockResults",
    "handleManualFinancialRestoreStockError");
  assert.match(stockRestore, /response\.query\s*[!=]==?\s*record\.request\.query|record\.request\.query\s*[!=]==?\s*response\.query/u);
  assert.match(stockRestore, /response\.truncated/u);
  assert.match(stockRestore, /response\.totalRowCount[\s\S]*record\.request\.maximumResults/u);
  assert.match(stockRestore,
    /const expectedMarket = [\s\S]*record\.pending\.verifiedStock\.market/u);
  assert.match(stockRestore,
    /const expectedCode = [\s\S]*record\.pending\.verifiedStock\.code/u);
  assert.match(stockRestore,
    /verifyStockForRecord\([\s\S]*expectedMarket,[\s\S]*expectedCode/u);
  assert.match(stockRestore,
    /record\.restoreKind === "named-financial"[\s\S]*namedMatch\.length === 1/u);

  const prepareBlock = functionBody(app, "manualFinancialPrepareBlocked", "loadPlaylist");
  assert.match(prepareBlock, /state\.manualFinancialRestore\.entries\.size > 0/u);
  assert.match(prepareBlock, /manualFinancialWorkflow\.isPlaylistEntryPlayable\(item\)/u);
  assert.match(app,
    /prepareButton\.disabled = [^;]*manualFinancialBlocked/u);
  const requestCommand = functionBody(app, "requestPlayoutCommand", "requestPlayoutStatus");
  assert.match(requestCommand,
    /if \(command === "prepare"\)[\s\S]*if \(manualFinancialPrepareBlocked\(\)\)[\s\S]*return false/u);
});

test("NXT GraphE restore searches the raw master name but verifies the suffixed display identity", () => {
  const loadResults = functionBody(
    app,
    "handleManualFinancialRestoreLoadResults",
    "handleManualFinancialRestoreLoadError");
  assert.match(loadResults,
    /expectedMarket[\s\S]*startsWith\("nxt-"\)[\s\S]*endsWith\(suffix\)[\s\S]*slice\(0, -suffix\.length\)/u);
  assert.match(loadResults, /query: searchQuery/u);

  const stockResults = functionBody(
    app,
    "handleManualFinancialRestoreStockResults",
    "handleManualFinancialRestoreStockError");
  assert.match(stockResults,
    /item\.name === record\.pending\.stockName && item\.market === record\.pending\.market/u);
  assert.match(stockResults, /manualFinancialWorkflow\.verifyStockForRecord/u);
});

test("native root routes all operator families and invalidates every browser correlation", () => {
  assert.match(mainWindow, /TryHandleOperatorCatalogRequest\(request\.Type, request\.Payload\)/u);
  assert.match(mainWindow, /TryHandleManualFinancialRequest\(request\.Type, request\.Payload\)/u);
  assert.match(mainWindow, /case "request-manual-operator-data-status"/u);
  assert.match(mainWindow, /case "save-manual-net-sell-data"/u);
  assert.match(mainWindow, /case "save-vi-manual-list"/u);

  assert.match(mainWindow, /InvalidateOperatorCatalogBrowserRequests\(\)/u);
  assert.match(mainWindow, /InvalidateManualOperatorBrowserRequests\(\)/u);
  assert.match(mainWindow, /InvalidateManualFinancialBrowserRequests\(\)/u);

  assert.match(manualFinancialBridge, /_playoutCommandGate\.WaitAsync\(0,/u);
  assert.match(manualFinancialBridge, /_databaseActivityGate\.WaitAsync\(0,/u);
  assert.match(operatorCatalogBridge, /_playoutCommandGate\.WaitAsync\(0,/u);
  assert.match(operatorCatalogBridge, /_databaseActivityGate\.WaitAsync\(0,/u);
  assert.match(manualListsBridge, /TryEnterManualMutationGatesAsync/u);
  assert.match(manualListsBridge, /_playoutCommandGate\.WaitAsync\(0\)/u);
});

test("native operator writes share one fail-closed idle gate at their dispatch boundary", () => {
  const gateStart = playoutBridge.indexOf("private bool CanStartOperatorMutation(");
  const gateEnd = playoutBridge.indexOf("private void ShutdownPlayoutRuntime(", gateStart);
  assert.ok(gateStart >= 0 && gateEnd > gateStart);
  const gate = playoutBridge.slice(gateStart, gateEnd);
  assert.match(gate, /familyWriteQuarantined/u);
  assert.match(gate, /_lifetimeCancellation\.IsCancellationRequested/u);
  assert.match(gate, /_playoutCommandInFlight/u);
  assert.match(gate, /_playoutShutdownStarted/u);
  assert.match(gate, /IsBrowserCorrelationQuarantined\(\)/u);
  assert.match(gate, /IsEngineAvailable: engine is not null/u);
  assert.match(gate, /IsWorkflowAvailable: workflow is not null/u);
  assert.match(gate, /IsCommandAvailable: status\?\.IsCommandAvailable \?\? false/u);
  assert.match(gate, /IsPlayCompletionPending: status\?\.IsPlayCompletionPending \?\? true/u);
  assert.match(gate, /PreparedSceneName: status\?\.PreparedSceneName/u);
  assert.match(gate, /OnAirSceneName: status\?\.OnAirSceneName/u);
  assert.match(gate, /PreparedCutCode: workflowState\?\.PreparedCutCode/u);
  assert.match(gate, /OnAirCutCode: workflowState\?\.OnAirCutCode/u);
  assert.match(gate, /IsRefreshActive: refreshStatus\.IsActive/u);
  assert.match(gate, /IsRefreshTaskRunning: _legacyRefreshTask is \{ IsCompleted: false \}/u);
  assert.match(gate, /OperatorMutationGate\.CanStart\(snapshot\)/u);
  assert.match(operatorMutationGate, /snapshot is not null/u);
  assert.match(operatorMutationGate, /snapshot\.IsEngineAvailable/u);
  assert.match(operatorMutationGate, /snapshot\.IsWorkflowAvailable/u);

  assert.match(manualFinancialBridge,
    /CanStartOperatorMutation\(IsManualFinancialWriteQuarantined\(\)\)/u);
  assert.ok((manualFinancialBridge.match(/CanStartManualFinancialWrite\(\)/gu) || []).length >= 3);
  assert.match(operatorCatalogBridge,
    /CanStartOperatorMutation\(IsOperatorCatalogWriteQuarantined\(\)\)/u);
  assert.ok((operatorCatalogBridge.match(/CanStartOperatorCatalogWrite\(\)/gu) || []).length >= 2);
  assert.match(manualListsBridge,
    /CanStartOperatorMutation\(IsManualOperatorWriteQuarantined\(\)\)/u);
  assert.ok((manualListsBridge.match(
    /CanStartOperatorMutation\(IsManualOperatorWriteQuarantined\(\)\)/gu) || []).length >= 3);

  for (const write of [
    sliceBetween(
      manualListsBridge,
      "private async Task HandleManualNetSellWriteAsync(",
      "private async Task HandleViManualListReadAsync("),
    sliceBetween(
      manualListsBridge,
      "private async Task HandleViManualListWriteAsync(",
      "private async Task<bool> TryEnterManualMutationGatesAsync(")
  ]) {
    assert.match(write,
      /TryEnterManualMutationGatesAsync\([\s\S]*Interlocked\.Exchange\(ref _manualOperatorWriteInFlight, 1\)[\s\S]*await (?:store\.)?Write/su);
  }
});

test("an unknown write outcome globally quarantines GraphE, ThemeA/EList, FSell/VI and named playlists", () => {
  assert.match(playoutBridge,
    /private int _operatorMutationQuarantined;[\s\S]*private string\? _operatorMutationQuarantineMessage;/u);
  assert.match(playoutBridge,
    /private bool IsOperatorMutationQuarantined\(\) =>[\s\S]*_operatorMutationQuarantined/u);
  assert.match(playoutBridge,
    /private void LatchOperatorMutationQuarantine\(string message\)[\s\S]*Interlocked\.CompareExchange\(ref _operatorMutationQuarantined, 1, 0\)/u);

  for (const [surface, source, familyCheck, familyLatch] of [
    ["GraphE", manualFinancialBridge,
      "IsManualFinancialWriteQuarantined", "LatchManualFinancialWriteQuarantine"],
    ["ThemeA/EList", operatorCatalogBridge,
      "IsOperatorCatalogWriteQuarantined", "LatchOperatorCatalogWriteQuarantine"],
    ["FSell/VI", manualListsBridge,
      "IsManualOperatorWriteQuarantined", "QuarantineManualOperatorWrites"],
    ["named playlist", namedPlaylistBridge,
      "IsNamedPlaylistWriteQuarantined", "LatchNamedPlaylistWriteQuarantine"]
  ]) {
    assert.match(source, new RegExp(
      `private bool ${familyCheck}\\(\\) =>[\\s\\S]{0,180}IsOperatorMutationQuarantined\\(\\)`, "u"),
    `${surface} must observe the process-wide quarantine`);
    assert.match(source, new RegExp(
      `private void ${familyLatch}\\(string message\\)[\\s\\S]{0,260}LatchOperatorMutationQuarantine\\(message\\)`, "u"),
    `${surface} must latch the process-wide quarantine`);
  }
});

test("malformed operator payloads retain a family-specific fail-closed fallback", () => {
  assert.match(manualFinancialBridge,
    /TryHandleManualFinancialRequest\(string\? requestType, JsonElement payload\)[\s\S]*case "create-manual-financial-record"[\s\S]*return true/u);
  assert.match(manualFinancialBridge,
    /private static string SafeManualFinancialRequestId\(JsonElement payload\) =>[\s\S]*payload\.ValueKind == JsonValueKind\.Object/u);
  assert.match(operatorCatalogBridge,
    /TryHandleOperatorCatalogRequest\(string\? requestType, JsonElement payload\)[\s\S]*case "create-theme-catalog"[\s\S]*case "create-expert-catalog"[\s\S]*return true/u);
  assert.match(operatorCatalogBridge,
    /private static string SafeOperatorCatalogRequestId\(JsonElement payload\)[\s\S]*payload\.ValueKind == JsonValueKind\.Object/u);

  for (const [parser, nextParser, readToken] of [
    ["TryParseAudienceRequest", "SafeManualOperatorRequestId", 'GetString(payload, "audience")'],
    ["TryParseManualNetSellWrite", "TryParseViWrite", 'GetString(payload, "audience")'],
    ["TryParseViWrite", "TryGetViExpectedVersion", "TryGetViExpectedVersion(payload"]
  ]) {
    const body = sliceBetween(
      manualListsBridge,
      `private static bool ${parser}(`,
      `private static ${nextParser === "SafeManualOperatorRequestId" ? "string" : "bool"} ${nextParser}(`);
    assert.match(body, /requestId = SafeManualOperatorRequestId\(payload\)/u, parser);
    const objectGuard = body.indexOf("payload.ValueKind");
    const firstPropertyRead = body.indexOf(readToken);
    assert.ok(objectGuard >= 0 && firstPropertyRead > objectGuard,
      `${parser} must check object kind before reading a property`);
  }
  assert.ok((manualListsBridge.match(/"INVALID_REQUEST"/gu) || []).length >= 5);

  const viVersion = sliceBetween(
    manualListsBridge,
    "private static bool TryGetViExpectedVersion(",
    "private static bool TryGetManualValue(");
  assert.ok(viVersion.indexOf("payload.ValueKind") <
    viVersion.indexOf('payload.TryGetProperty("expectedVersion"'));

  assert.match(mainWindow,
    /case "request-named-playlist-list":[\s\S]*case "delete-named-playlist":[\s\S]*PostNamedPlaylistError\([\s\S]*"INVALID_REQUEST"/u);
});

test("window shutdown disposes both trusted manual stores and the one-time importer after their gate is idle", () => {
  const closed = sliceBetween(mainWindow, "private void OnClosed(", "private sealed record WebRequest(");
  assert.match(closed, /ShutdownManualOperatorDataRuntime\(\)/u);

  const shutdown = sliceBetween(
    manualListsBridge,
    "private void ShutdownManualOperatorDataRuntime(",
    "private async Task DisposeManualOperatorStoresWhenIdleAsync(");
  assert.match(shutdown, /InvalidateManualOperatorBrowserRequests\(\)/u);
  assert.match(shutdown, /Interlocked\.Exchange\(ref _manualNetSellStore, null\)/u);
  assert.match(shutdown, /Interlocked\.Exchange\(ref _viManualListStore, null\)/u);
  assert.match(shutdown, /Interlocked\.Exchange\(ref _legacyManualOperatorDataImporter, null\)/u);
  assert.match(shutdown, /DisposeManualOperatorStoresWhenIdleAsync\(netSellStore, viStore, importer\)/u);

  const dispose = sliceBetween(
    manualListsBridge,
    "private async Task DisposeManualOperatorStoresWhenIdleAsync(",
    "private void InvalidateManualOperatorBrowserRequests(");
  assert.match(dispose, /await _manualOperatorDataGate\.WaitAsync\(\)/u);
  assert.match(dispose, /importer\?\.Dispose\(\)/u);
  assert.match(dispose, /viStore\?\.Dispose\(\)/u);
  assert.match(dispose, /netSellStore\?\.Dispose\(\)/u);
  assert.match(dispose, /_manualOperatorDataGate\.Release\(\)/u);
});

test("system close requires an explicit default-cancel confirmation without an automatic playout command", () => {
  assert.match(mainWindow,
    /InitializeComponent\(\);\s*LogNativeOperatorStartup\(\);\s*EnsureCloseConfirmationAttached\(\);/u);
  assert.match(mainWindow,
    /private void OnClosed\([\s\S]*DetachCloseConfirmation\(\);[\s\S]*ShutdownPlayoutRuntime\(\)/u);

  assert.match(closeConfirmationBridge,
    /appWindow\.Closing \+= OnAppWindowClosing/u);
  assert.match(closeConfirmationBridge,
    /if \(Volatile\.Read\(ref _closeConfirmed\) != 0\)[\s\S]*args\.Cancel = true/u);
  assert.match(closeConfirmationBridge,
    /Interlocked\.CompareExchange\(ref _closeConfirmationPending, 1, 0\)/u);
  assert.match(closeConfirmationBridge,
    /DefaultButton = ContentDialogButton\.Close/u);
  assert.match(closeConfirmationBridge,
    /result != ContentDialogResult\.Primary/u);
  assert.match(closeConfirmationBridge,
    /Interlocked\.Exchange\(ref _closeConfirmed, 1\);\s*Close\(\);/u);
  assert.match(closeConfirmationBridge,
    /appWindow\.Closing -= OnAppWindowClosing/u);
  assert.match(closeConfirmationBridge,
    /종료 과정에서는 TAKE OUT을 자동 실행하지 않습니다/u);
  assert.doesNotMatch(closeConfirmationBridge,
    /TakeOutAsync|StopAll|StopAllAsync|playout-command/u);
});

test("operator modals isolate global playout and playlist keyboard shortcuts", () => {
  const modalGate = functionBody(app, "hasOpenOperatorModal", "isolateOperatorModalShortcut");
  assert.match(modalGate,
    /dialog\[open\], \[role="dialog"\]\[aria-modal="true"\]/u);
  assert.match(modalGate, /!element\.hidden && !element\.closest\("\[hidden\]"\)/u);

  const isolation = functionBody(app, "isolateOperatorModalShortcut", null);
  assert.match(isolation, /if \(!hasOpenOperatorModal\(\)\) return false/u);
  for (const key of ["F2", "F3", "F8", "Escape", "Home", "End", "Delete"]) {
    assert.match(isolation, new RegExp(`"${key}"`, "u"));
  }
  assert.match(isolation, /event\.ctrlKey && key\.toLowerCase\(\) === "k"/u);
  assert.match(isolation, /showToast\("편집 창을 먼저 닫은 뒤 송출 명령을 실행하세요\."\)/u);

  const keyHandler = app.slice(app.indexOf('document.addEventListener("keydown"'));
  const gate = keyHandler.indexOf("if (isolateOperatorModalShortcut(event)) return;");
  const playout = keyHandler.indexOf('if (["F2", "F3", "F8", "Escape"].includes(event.key))');
  const playlistDelete = keyHandler.indexOf('if (event.key === "Delete")');
  assert.ok(gate >= 0 && playout > gate && playlistDelete > gate);
});

test("generic native bridge errors are bounded and visible without inventing correlation", () => {
  const bridgeError = functionBody(app, "handleBridgeError", "handleNativeMessage");
  assert.match(bridgeError, /rawMessage\.length <= 512/u);
  assert.match(bridgeError, /Native Bridge가 올바르지 않은 요청을 거부했습니다/u);
  assert.match(bridgeError, /addLog\(`Native Bridge 요청 오류 · \$\{message\}`\)/u);
  assert.match(bridgeError, /showToast\(message\)/u);
  assert.doesNotMatch(bridgeError,
    /pendingCommand\s*=\s*null|clearPlayoutError|requestPlayoutCommand|retry/u);

  const nativeSwitch = functionBody(app, "handleNativeMessage", null);
  assert.match(nativeSwitch,
    /case "bridge-error":\s*handleBridgeError\(message\.payload\);\s*break;/u);
});

test("GraphE async UI boundaries retain the dispatcher context before posting WebView replies", () => {
  assert.doesNotMatch(manualFinancialBridge,
    /operationBody\(requestToken\)\.ConfigureAwait\(false\)/u);
  assert.doesNotMatch(manualFinancialBridge,
    /preflight\(requestToken\)\.ConfigureAwait\(false\)/u);
  assert.doesNotMatch(manualFinancialBridge,
    /operationBody\(service,\s*requestToken\)\.ConfigureAwait\(false\)/u);
});
