const test = require("node:test");
const assert = require("node:assert/strict");
const workflow = require("../../Web/manual-financial-workflow.js");

const version = "A".repeat(64);

function quarters() {
  return [
    { quarter: "1Q", value: 10 },
    { quarter: "2Q", value: -20 },
    { quarter: "3Q", value: 30 },
    { quarter: "4Q", value: 40 },
    { quarter: "5Q", value: 50 },
    { quarter: "6Q", value: 60 }
  ];
}

function record(screen, stockName = "Alpha") {
  switch (screen) {
    case "revenue-composition":
      return {
        kind: screen,
        stockName,
        baseDate: "2026-06",
        slices: [
          { label: "Semiconductor", percentageText: "50.0", percentage: 50 },
          { label: "Service", percentageText: "-", percentage: 0 },
          { label: "Overseas", percentageText: "25", percentage: 25 },
          null,
          null
        ]
      };
    case "growth-metrics":
      return {
        kind: screen,
        stockName,
        salesGrowth: [1, 2, 3, 4],
        operatingProfitGrowth: [5, 6, null, 8],
        netAssetGrowth: [9, 10, 11, 12],
        netIncomeGrowth: [13, 14, 15, 16],
        periods: ["1Q", "2Q", "3Q", "4Q"]
      };
    case "sales":
    case "operating-profit":
      return { kind: screen, stockName, quarters: quarters() };
    default:
      throw new Error("bad screen");
  }
}

function listRequest(screen = "sales") {
  return workflow.createListRequest("list-1", screen, "Alpha", 25);
}

function listPayload(screen = "sales", records = [record(screen)]) {
  return {
    requestId: "list-1",
    screen,
    query: "Alpha",
    retrievedAt: "2026-07-12T00:10:00+09:00",
    totalRowCount: records.length,
    truncated: false,
    items: records.map((item, index) => ({
      stockName: item.stockName,
      rowVersion: String.fromCharCode(65 + index).repeat(64),
      record: item
    }))
  };
}

function trustedSnapshot(screen = "sales", stockName = "Alpha") {
  const request = workflow.createListRequest("list-1", screen, "Alpha", 25);
  const response = workflow.normalizeListResponse(
    listPayload(screen, [record(screen, stockName)]),
    request);
  assert.ok(response);
  return response.items[0];
}

function stockPayload(results = [{
  market: "kospi",
  source: "oracle",
  name: "Alpha",
  code: "000001"
}]) {
  return {
    requestId: "stock-1",
    query: "Alpha",
    retrievedAt: "2026-07-12T00:11:00+09:00",
    totalRowCount: results.length,
    truncated: false,
    results
  };
}

function verifiedStock(recordValue = record("sales"), results) {
  const response = workflow.normalizeStockSearchResponse(stockPayload(results));
  assert.ok(response);
  const verified = workflow.verifyStockForRecord(
    recordValue,
    response,
    "kospi",
    "000001");
  assert.ok(verified);
  return verified;
}

function trustedSelection(snapshot, stock, requestId = "selection-1") {
  const request = workflow.createSelectionRequest(requestId, snapshot, stock);
  const profile = workflow.getScreenDefinition(snapshot.screen);
  const groupCode = {
    kospi: "KOSPI",
    kosdaq: "KOSDAQ",
    "nxt-kospi": "NXT_KOSPI",
    "nxt-kosdaq": "NXT_KOSDAQ"
  }[stock.market];
  const response = workflow.normalizeSelectionResponse({
    requestId,
    screen: snapshot.screen,
    stockName: snapshot.stockName,
    rowVersion: snapshot.rowVersion,
    builderKey: profile.builderKey,
    code: profile.code,
    aliases: [profile.code],
    selection: {
      groupCode,
      subject: snapshot.stockName,
      graphicType: profile.graphicType,
      subtype: "",
      dataCode: ""
    },
    currentPage: 1,
    totalPages: 1
  }, request);
  assert.ok(response);
  return response;
}

function trustedLoad(snapshot, requestId = "load-1") {
  const request = workflow.createLoadRequest(requestId, snapshot.screen, snapshot.stockName);
  const response = workflow.normalizeLoadResponse({
    requestId,
    screen: snapshot.screen,
    retrievedAt: "2026-07-12T00:20:00+09:00",
    snapshot: {
      stockName: snapshot.stockName,
      rowVersion: snapshot.rowVersion,
      record: snapshot.record
    }
  }, request);
  assert.ok(response);
  return response;
}

test("workflow pins GraphE Oracle tables, CRUD bridge and four scene actions", () => {
  assert.deepEqual(workflow.bridgeContract, {
    requestErrorType: "manual-financial-request-error",
    listRequestType: "request-manual-financial-list",
    listResultType: "manual-financial-list-results",
    listErrorType: "manual-financial-list-error",
    loadRequestType: "request-manual-financial-load",
    loadResultType: "manual-financial-load-results",
    loadErrorType: "manual-financial-load-error",
    selectionRequestType: "request-manual-financial-selection",
    selectionResultType: "manual-financial-selection-results",
    selectionErrorType: "manual-financial-selection-error",
    createRequestType: "create-manual-financial-record",
    updateRequestType: "update-manual-financial-record",
    deleteRequestType: "delete-manual-financial-record",
    deleteAllRequestType: "delete-all-manual-financial-records",
    mutationResultType: "manual-financial-mutation-result",
    mutationErrorType: "manual-financial-mutation-error",
    defaultMaximumResults: 200,
    maximumResults: 1000,
    maximumQueryLength: 64
  });
  assert.equal(workflow.sourceContract.originalForm, "Form/GraphE.cs");
  assert.equal(workflow.sourceContract.provider, "oracle");
  assert.equal(workflow.sourceContract.operatorSchemaVersion, 1);
  assert.equal(workflow.sourceContract.restoreRequiresNativeLoad, true);
  assert.equal(workflow.sourceContract.rawNamedPlaylistRowPlayable, false);
  assert.equal(workflow.sourceContract.writeSafety.outcomeUnknownAutomaticRetry, false);
  assert.deepEqual(workflow.actions.map(action => [action.screen, action.builderKey, action.code]), [
    ["revenue-composition", "s5076", "5076"],
    ["growth-metrics", "s5079", "5079"],
    ["sales", "s5080", "5080"],
    ["operating-profit", "s5081", "5081"]
  ]);
});

test("generic native request errors are exact and operation-correlated", () => {
  const error = {
    requestId: "",
    operation: "load",
    code: "INVALID_REQUEST",
    message: "Invalid screen."
  };
  assert.deepEqual(workflow.normalizeRequestError(error, "load"), error);
  assert.equal(workflow.normalizeRequestError(error, "list"), null);
  assert.equal(workflow.normalizeRequestError({ ...error, extra: true }, "load"), null);
  assert.equal(workflow.normalizeRequestError({ ...error, requestId: "bad id" }, "load"), null);
  assert.equal(workflow.normalizeRequestError({ ...error, operation: "unknown" }), null);
  assert.equal(workflow.normalizeRequestError({ ...error, code: "OTHER" }), null);
  assert.equal(workflow.normalizeRequestError({ ...error, message: "bad\nmessage" }), null);
});

test("all closed records serialize exactly to the existing underscore loader shapes", () => {
  assert.deepEqual(workflow.serializeRecordForStorage(
    "revenue-composition",
    record("revenue-composition")), {
    table: "INPUT_PIE",
    columns: ["STOCK_NAME", "BASE_DATE", "GUSUNG_1", "GUSUNG_2", "GUSUNG_3", "GUSUNG_4", "GUSUNG_5"],
    values: ["Alpha", "2026-06", "Semiconductor_50.0", "Service_-", "Overseas_25", "", ""]
  });
  assert.deepEqual(workflow.serializeRecordForStorage(
    "growth-metrics",
    record("growth-metrics")), {
    table: "INPUT_GROW",
    columns: ["STOCK_NAME", "GUSUNG_1", "GUSUNG_2", "GUSUNG_3", "GUSUNG_4", "BASE_DATE"],
    values: ["Alpha", "1_2_3_4", "5_6__8", "9_10_11_12", "13_14_15_16", "1Q_2Q_3Q_4Q"]
  });
  assert.deepEqual(workflow.serializeRecordForStorage("sales", record("sales")).values, [
    "Alpha", "1Q_10", "2Q_-20", "3Q_30", "4Q_40", "5Q_50", "6Q_60"
  ]);
  assert.equal(workflow.serializeRecordForStorage(
    "operating-profit",
    record("operating-profit")).table, "INPUT_PROFIT");
});

test("closed DTO validation rejects malformed counts, separators and unsafe numbers", () => {
  const revenue = record("revenue-composition");
  assert.equal(workflow.normalizeRecord("revenue-composition", {
    ...revenue,
    slices: revenue.slices.slice(0, 4)
  }), null);
  assert.equal(workflow.normalizeRecord("revenue-composition", {
    ...revenue,
    slices: [{ label: "bad_label", percentageText: "50", percentage: 50 }, ...revenue.slices.slice(1)]
  }), null);
  assert.equal(workflow.normalizeRecord("revenue-composition", {
    ...revenue,
    slices: [{ label: "A", percentageText: "50", percentage: 49 }, ...revenue.slices.slice(1)]
  }), null);
  assert.equal(workflow.normalizeRecord("growth-metrics", {
    ...record("growth-metrics"),
    salesGrowth: [1, 2, Number.POSITIVE_INFINITY, 4]
  }), null);
  assert.equal(workflow.normalizeRecord("sales", {
    ...record("sales"),
    quarters: quarters().slice(1)
  }), null);
  assert.equal(workflow.normalizeRecord("sales", {
    ...record("sales"),
    stockName: "bad\nname"
  }), null);
});

test("list requests normalize blank/search text and enforce exact screen bounds", () => {
  assert.deepEqual(workflow.createListRequest("list-1", "sales", "   "), {
    requestId: "list-1",
    screen: "sales",
    query: ""
  });
  assert.deepEqual(listRequest(), {
    requestId: "list-1",
    screen: "sales",
    query: "Alpha",
    maximumResults: 25
  });
  assert.throws(() => workflow.createListRequest("bad id", "sales"), /request id/);
  assert.throws(() => workflow.createListRequest("list", "unknown"), /screen/);
  assert.throws(() => workflow.createListRequest("list", "sales", "", 1001), /limited/);
});

test("native list responses create trusted snapshots and reject duplicate or reordered names", () => {
  const request = workflow.createListRequest("list-1", "sales", "Alpha", 25);
  const payload = listPayload("sales", [record("sales", "Alpha"), record("sales", "Beta")]);
  const normalized = workflow.normalizeListResponse(payload, request);
  assert.ok(normalized);
  assert.equal(normalized.items[0].rowVersion, version);
  assert.ok(Object.isFrozen(normalized.items));

  assert.equal(workflow.normalizeListResponse({
    ...payload,
    items: [...payload.items].reverse()
  }, request), null);
  assert.equal(workflow.normalizeListResponse(listPayload("sales", [
    record("sales", "Alpha"), record("sales", "ALPHA")
  ]), request), null);
  assert.equal(workflow.normalizeListResponse({ ...payload, requestId: "stale" }, request), null);
  assert.equal(workflow.normalizeListResponse({
    ...payload,
    items: [{ ...payload.items[0], rowVersion: "bad" }, payload.items[1]]
  }, request), null);
});

test("list errors are exact and correlated", () => {
  const request = listRequest();
  assert.deepEqual(workflow.normalizeListError({
    requestId: "list-1",
    screen: "sales",
    query: "Alpha",
    message: "Database unavailable"
  }, request), {
    requestId: "list-1",
    screen: "sales",
    query: "Alpha",
    message: "Database unavailable"
  });
  assert.equal(workflow.normalizeListError({
    requestId: "other",
    screen: "sales",
    query: "Alpha",
    message: "Database unavailable"
  }, request), null);
});

test("load responses are exact native refreshes and never trust a stale request", () => {
  const snapshot = trustedSnapshot("sales");
  const request = workflow.createLoadRequest("load-1", "sales", "Alpha");
  assert.deepEqual(request, {
    requestId: "load-1",
    screen: "sales",
    stockName: "Alpha"
  });
  const loaded = trustedLoad(snapshot);
  assert.deepEqual(loaded.snapshot.record, snapshot.record);
  assert.equal(workflow.normalizeLoadResponse({
    requestId: "load-1",
    screen: "sales",
    retrievedAt: "2026-07-12T00:20:00+09:00",
    snapshot: {
      stockName: "Beta",
      rowVersion: snapshot.rowVersion,
      record: record("sales", "Beta")
    }
  }, request), null);
  assert.deepEqual(workflow.normalizeLoadError({
    requestId: "load-1",
    screen: "sales",
    stockName: "Alpha",
    code: "NOT_FOUND",
    message: "Missing",
    retryable: false
  }, request).code, "NOT_FOUND");
});

test("stock master normalization enforces provider identity and rejects duplicate rows", () => {
  const normalized = workflow.normalizeStockSearchResponse(stockPayload());
  assert.ok(normalized);
  assert.equal(normalized.results[0].source, "oracle");
  assert.equal(workflow.normalizeStockSearchResponse(stockPayload([{
    market: "kospi", source: "mariaDb", name: "Alpha", code: "000001"
  }])), null);
  assert.equal(workflow.normalizeStockSearchResponse(stockPayload([
    { market: "kospi", source: "oracle", name: "Alpha", code: "000001" },
    { market: "kospi", source: "oracle", name: "Alpha", code: "000001" }
  ])), null);
});

test("verification rejects same-name market ambiguity and fabricated responses", () => {
  const value = record("sales");
  assert.equal(workflow.verifyStockForRecord(value, stockPayload(), "kospi", "000001"), null);
  const response = workflow.normalizeStockSearchResponse(stockPayload([
    { market: "kospi", source: "oracle", name: "Alpha", code: "000001" },
    { market: "kosdaq", source: "oracle", name: "Alpha", code: "000002" }
  ]));
  assert.ok(response);
  assert.equal(workflow.verifyStockForRecord(value, response, "kospi", "000001"), null);
  const one = workflow.normalizeStockSearchResponse(stockPayload());
  assert.equal(workflow.verifyStockForRecord(value, one, "kosdaq", "000001"), null);
});

test("selection requests bind snapshot version and freshly verified stock to native result", () => {
  const snapshot = trustedSnapshot("sales");
  const stock = verifiedStock(snapshot.record);
  const request = workflow.createSelectionRequest("selection-1", snapshot, stock);
  assert.deepEqual(request, {
    requestId: "selection-1",
    screen: "sales",
    stockName: "Alpha",
    rowVersion: version,
    stock: {
      market: "kospi",
      source: "oracle",
      name: "Alpha",
      code: "000001"
    }
  });
  const response = trustedSelection(snapshot, stock);
  assert.equal(response.builderKey, "s5080");
  assert.equal(workflow.normalizeSelectionResponse({
    ...response,
    rowVersion: "B".repeat(64)
  }, request), null);
  assert.deepEqual(workflow.normalizeSelectionError({
    requestId: "selection-1",
    screen: "sales",
    stockName: "Alpha",
    code: "STALE_ROW",
    message: "Reload",
    retryable: false
  }, request).code, "STALE_ROW");
});

test("create requires a verified exact stock and sends no row version", () => {
  const value = record("sales");
  const stock = verifiedStock(value);
  assert.deepEqual(workflow.createCreateRequest("create-1", value, stock), {
    requestId: "create-1",
    screen: "sales",
    stock: {
      market: "kospi",
      source: "oracle",
      name: "Alpha",
      code: "000001"
    },
    record: value
  });
  assert.throws(() => workflow.createCreateRequest("create-1", value, {
    market: "kospi", source: "oracle", name: "Alpha", code: "000001"
  }), /verified stock/);
  assert.throws(() => workflow.createCreateRequest(
    "create-1",
    record("sales", "Beta"),
    stock), /verified stock/);
});

test("update and single delete require trusted snapshots and optimistic row version", () => {
  const snapshot = trustedSnapshot("sales");
  const replacement = record("sales");
  assert.deepEqual(workflow.createUpdateRequest("update-1", snapshot, replacement), {
    requestId: "update-1",
    screen: "sales",
    stockName: "Alpha",
    expectedRowVersion: version,
    record: replacement
  });
  assert.deepEqual(workflow.createDeleteRequest("delete-1", snapshot), {
    requestId: "delete-1",
    screen: "sales",
    stockName: "Alpha",
    expectedRowVersion: version
  });
  assert.throws(() => workflow.createUpdateRequest(
    "update-1",
    snapshot,
    record("sales", "Beta")), /cannot rename/);
  assert.throws(() => workflow.createDeleteRequest("delete-1", {
    ...snapshot
  }), /native-normalized/);
});

test("delete-all requires the exact table-specific confirmation token", () => {
  assert.deepEqual(workflow.createDeleteAllRequest(
    "delete-all-1",
    "sales",
    "DELETE_ALL_INPUT_SELL"), {
    requestId: "delete-all-1",
    screen: "sales",
    confirmationToken: "DELETE_ALL_INPUT_SELL"
  });
  assert.throws(() => workflow.createDeleteAllRequest(
    "delete-all-1",
    "sales",
    "DELETE_ALL_INPUT_PROFIT"), /confirmation token/);
});

test("mutation results correlate and errors can never request automatic retry", () => {
  const snapshot = trustedSnapshot("sales");
  const request = workflow.createDeleteRequest("delete-1", snapshot);
  assert.deepEqual(workflow.normalizeMutationResult({
    requestId: "delete-1",
    operationId: "operation-1",
    operation: "delete-one",
    screen: "sales",
    stockName: "Alpha",
    committedAt: "2026-07-12T00:12:00+09:00",
    affectedRows: 1
  }, request), {
    requestId: "delete-1",
    operationId: "operation-1",
    operation: "delete-one",
    screen: "sales",
    stockName: "Alpha",
    committedAt: "2026-07-12T00:12:00+09:00",
    affectedRows: 1
  });
  assert.deepEqual(workflow.normalizeMutationError({
    requestId: "delete-1",
    operation: "delete-one",
    screen: "sales",
    stockName: "Alpha",
    code: "OUTCOME_UNKNOWN",
    message: "Do not retry automatically",
    retryable: false,
    outcomeUnknown: true
  }, request).outcomeUnknown, true);
  assert.equal(workflow.normalizeMutationResult({
    requestId: "delete-1",
    operationId: "operation-1",
    operation: "update",
    screen: "sales",
    stockName: "Alpha",
    committedAt: "2026-07-12T00:12:00+09:00",
    affectedRows: 1
  }, request), null);
  assert.equal(workflow.normalizeMutationError({
    requestId: "delete-1",
    operation: "delete-one",
    screen: "sales",
    stockName: "Alpha",
    code: "OUTCOME_UNKNOWN",
    message: "retry",
    retryable: true,
    outcomeUnknown: true
  }, request), null);
});

test("only trusted DB snapshots and verified master rows create the four exact one-page cuts", () => {
  const expectations = [
    ["revenue-composition", "s5076", "5076", "REVENUE_COMPOSITION"],
    ["growth-metrics", "s5079", "5079", "GROWTH_METRICS"],
    ["sales", "s5080", "5080", "SALES"],
    ["operating-profit", "s5081", "5081", "OPERATING_PROFIT"]
  ];
  for (const [screen, builderKey, code, graphicType] of expectations) {
    const snapshot = trustedSnapshot(screen);
    const stock = verifiedStock(snapshot.record);
    assert.deepEqual(workflow.buildSelection(snapshot, stock), {
      builderKey,
      code,
      aliases: [code],
      selection: {
        groupCode: "KOSPI",
        subject: "Alpha",
        graphicType,
        subtype: "",
        dataCode: ""
      },
      currentPage: 1,
      pageCount: 1
    });
  }
  const rawSnapshot = listPayload("sales").items[0];
  const stock = verifiedStock(record("sales"));
  assert.equal(workflow.buildSelection(rawSnapshot, stock), null);
});

test("playlist entry has the complete standard operator metadata and native trust", () => {
  const snapshot = trustedSnapshot("operating-profit");
  const stock = verifiedStock(snapshot.record);
  const selection = trustedSelection(snapshot, stock);
  const entry = workflow.createPlaylistEntry(snapshot, stock, selection, {
    id: "profit-1",
    fadeDuration: 8
  });
  assert.deepEqual(entry, {
    id: "profit-1",
    builderKey: "s5081",
    code: "5081",
    aliases: ["5081"],
    title: "Alpha",
    detail: "영업이익",
    category: "manual-financial",
    market: "kospi",
    stockName: "Alpha",
    stockCode: "000001",
    isNxt: false,
    selection: {
      groupCode: "KOSPI",
      subject: "Alpha",
      graphicType: "OPERATING_PROFIT",
      subtype: "",
      dataCode: ""
    },
    enabled: true,
    fadeDuration: 8,
    graphicType: "OPERATING_PROFIT",
    subtype: "",
    currentPage: 1,
    pageCount: 1,
    operator: {
      source: "manual-financial-workflow",
      schemaVersion: 1,
      screen: "operating-profit",
      snapshot: {
        screen: "operating-profit",
        stockName: "Alpha",
        rowVersion: version,
        record: snapshot.record
      },
      verifiedStock: {
        market: "kospi",
        source: "oracle",
        name: "Alpha",
        code: "000001"
      }
    }
  });
  assert.equal(workflow.isPlaylistEntryPlayable(entry), true);
  assert.equal(workflow.isPlaylistEntryPlayable(JSON.parse(JSON.stringify(entry))), false);
  assert.throws(() => workflow.createPlaylistEntry(snapshot, stock, {
    ...selection
  }, { id: "forged" }), /native-confirmed/);
});

test("enabled replacement keeps native financial trust without mutating the frozen entry", () => {
  const snapshot = trustedSnapshot("sales");
  const stock = verifiedStock(snapshot.record);
  const selection = trustedSelection(snapshot, stock);
  const entry = workflow.createPlaylistEntry(snapshot, stock, selection, {
    id: "sales-toggle",
    fadeDuration: 6
  });
  const replacement = workflow.withEnabled(entry, false);

  assert.notEqual(replacement, entry);
  assert.equal(Object.isFrozen(replacement), true);
  assert.equal(entry.enabled, true);
  assert.equal(replacement.enabled, false);
  assert.equal(workflow.isPlaylistEntryPlayable(replacement), true);
  assert.equal(workflow.withEnabled({ ...entry }, false), null);
});

test("stored entries restore only as non-playable refresh-required descriptors", () => {
  const snapshot = trustedSnapshot("sales");
  const stock = verifiedStock(snapshot.record);
  const selection = trustedSelection(snapshot, stock);
  const live = workflow.createPlaylistEntry(snapshot, stock, selection, {
    id: "sales-restore",
    fadeDuration: 7,
    enabled: false
  });
  const stored = JSON.parse(JSON.stringify(live));
  const pending = workflow.restorePlaylistEntry(stored);
  assert.ok(pending);
  assert.equal(pending.status, "refresh-required");
  assert.equal(pending.playable, false);
  assert.equal(workflow.isPlaylistEntryPlayable(pending.entry), false);
  assert.deepEqual(workflow.createRestoreLoadRequest("restore-load", pending), {
    requestId: "restore-load",
    screen: "sales",
    stockName: "Alpha"
  });

  assert.equal(workflow.restorePlaylistEntry({
    ...stored,
    selection: { ...stored.selection, dataCode: "tampered" }
  }), null);
  assert.equal(workflow.restorePlaylistEntry({
    ...stored,
    operator: { ...stored.operator, schemaVersion: 2 }
  }), null);
});

test("native load, fresh stock verification and native selection materialize a stored entry", () => {
  const originalSnapshot = trustedSnapshot("sales");
  const originalStock = verifiedStock(originalSnapshot.record);
  const originalSelection = trustedSelection(originalSnapshot, originalStock);
  const stored = JSON.parse(JSON.stringify(workflow.createPlaylistEntry(
    originalSnapshot,
    originalStock,
    originalSelection,
    { id: "sales-restore", fadeDuration: 7, enabled: false })));
  const pending = workflow.restorePlaylistEntry(stored);

  const loadRequest = workflow.createRestoreLoadRequest("restore-load", pending);
  const loadResponse = workflow.normalizeLoadResponse({
    requestId: "restore-load",
    screen: "sales",
    retrievedAt: "2026-07-12T00:20:00+09:00",
    snapshot: {
      stockName: stored.operator.snapshot.stockName,
      rowVersion: stored.operator.snapshot.rowVersion,
      record: stored.operator.snapshot.record
    }
  }, loadRequest);
  assert.ok(loadResponse);
  const freshStockResponse = workflow.normalizeStockSearchResponse(stockPayload());
  const freshStock = workflow.verifyStockForRecord(
    loadResponse.snapshot.record,
    freshStockResponse,
    pending.verifiedStock.market,
    pending.verifiedStock.code);
  assert.ok(freshStock);
  const selectionRequest = workflow.createRestoreSelectionRequest(
    "restore-selection",
    pending,
    loadResponse,
    freshStock);
  const profile = workflow.getScreenDefinition("sales");
  const selectionResponse = workflow.normalizeSelectionResponse({
    requestId: "restore-selection",
    screen: "sales",
    stockName: "Alpha",
    rowVersion: version,
    builderKey: profile.builderKey,
    code: profile.code,
    aliases: [profile.code],
    selection: {
      groupCode: "KOSPI",
      subject: "Alpha",
      graphicType: profile.graphicType,
      subtype: "",
      dataCode: ""
    },
    currentPage: 1,
    totalPages: 1
  }, selectionRequest);
  const restored = workflow.materializeRestoredPlaylistEntry(
    pending,
    loadResponse,
    freshStock,
    selectionResponse);
  assert.deepEqual(restored, stored);
  assert.equal(workflow.isPlaylistEntryPlayable(restored), true);
});

test("row-version or record drift keeps a stored entry permanently unplayable", () => {
  const snapshot = trustedSnapshot("sales");
  const stock = verifiedStock(snapshot.record);
  const selection = trustedSelection(snapshot, stock);
  const stored = JSON.parse(JSON.stringify(workflow.createPlaylistEntry(
    snapshot,
    stock,
    selection,
    { id: "stale-entry" })));
  const pending = workflow.restorePlaylistEntry(stored);
  const request = workflow.createRestoreLoadRequest("restore-load", pending);
  const changed = workflow.normalizeLoadResponse({
    requestId: "restore-load",
    screen: "sales",
    retrievedAt: "2026-07-12T00:20:00+09:00",
    snapshot: {
      stockName: "Alpha",
      rowVersion: "B".repeat(64),
      record: record("sales")
    }
  }, request);
  const freshStockResponse = workflow.normalizeStockSearchResponse(stockPayload());
  const freshStock = workflow.verifyStockForRecord(
    changed.snapshot.record,
    freshStockResponse,
    "kospi",
    "000001");
  assert.throws(() => workflow.createRestoreSelectionRequest(
    "restore-selection",
    pending,
    changed,
    freshStock), /did not reproduce/);
  assert.equal(workflow.materializeRestoredPlaylistEntry(
    pending,
    changed,
    freshStock,
    selection), null);
  assert.equal(workflow.isPlaylistEntryPlayable(stored), false);
});
