(function (root, factory) {
  "use strict";

  const api = Object.freeze(factory());
  if (typeof module === "object" && module.exports) module.exports = api;
  if (root) root.MbnManualFinancialWorkflow = api;
})(typeof globalThis === "object" ? globalThis : this, function () {
  "use strict";

  const DEFAULT_MAXIMUM_RESULTS = 200;
  const MAXIMUM_RESULTS = 1000;
  const MAXIMUM_QUERY_LENGTH = 64;
  const DEFAULT_FADE_DURATION = 6;
  const OPERATOR_SCHEMA_VERSION = 1;
  const MAXIMUM_FLOAT = 1_000_000_000_000;
  const int32Minimum = -2147483648;
  const int32Maximum = 2147483647;
  const requestIdPattern = /^[A-Za-z0-9_-]{1,128}$/;
  const stockCodePattern = /^[A-Za-z0-9]{1,32}$/;
  const rowVersionPattern = /^[0-9A-F]{64}$/;
  const numberTextPattern = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$/;
  const unsafeTextPattern = /[\p{Cc}\p{Cf}\p{Cs}\p{Zl}\p{Zp}\uFFFD]/u;

  const bridgeContract = Object.freeze({
    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: DEFAULT_MAXIMUM_RESULTS,
    maximumResults: MAXIMUM_RESULTS,
    maximumQueryLength: MAXIMUM_QUERY_LENGTH
  });

  const screenDefinitions = Object.freeze({
    "revenue-composition": Object.freeze({
      screen: "revenue-composition",
      table: "INPUT_PIE",
      label: "주요매출 구성",
      builderKey: "s5076",
      code: "5076",
      aliases: Object.freeze(["5076"]),
      graphicType: "REVENUE_COMPOSITION",
      storageColumns: Object.freeze([
        "STOCK_NAME", "BASE_DATE", "GUSUNG_1", "GUSUNG_2",
        "GUSUNG_3", "GUSUNG_4", "GUSUNG_5"
      ]),
      storageShape: "BASE_DATE + five label_percentage columns",
      deleteAllConfirmation: "DELETE_ALL_INPUT_PIE"
    }),
    "growth-metrics": Object.freeze({
      screen: "growth-metrics",
      table: "INPUT_GROW",
      label: "성장성 지표",
      builderKey: "s5079",
      code: "5079",
      aliases: Object.freeze(["5079"]),
      graphicType: "GROWTH_METRICS",
      storageColumns: Object.freeze([
        "STOCK_NAME", "GUSUNG_1", "GUSUNG_2", "GUSUNG_3",
        "GUSUNG_4", "BASE_DATE"
      ]),
      storageShape: "four value_value_value_value series + BASE_DATE periods",
      deleteAllConfirmation: "DELETE_ALL_INPUT_GROW"
    }),
    sales: Object.freeze({
      screen: "sales",
      table: "INPUT_SELL",
      label: "매출액",
      builderKey: "s5080",
      code: "5080",
      aliases: Object.freeze(["5080"]),
      graphicType: "SALES",
      storageColumns: Object.freeze([
        "STOCK_NAME", "GUSUNG_1", "GUSUNG_2", "GUSUNG_3",
        "GUSUNG_4", "GUSUNG_5", "GUSUNG_6"
      ]),
      storageShape: "six quarter_integer columns",
      deleteAllConfirmation: "DELETE_ALL_INPUT_SELL"
    }),
    "operating-profit": Object.freeze({
      screen: "operating-profit",
      table: "INPUT_PROFIT",
      label: "영업이익",
      builderKey: "s5081",
      code: "5081",
      aliases: Object.freeze(["5081"]),
      graphicType: "OPERATING_PROFIT",
      storageColumns: Object.freeze([
        "STOCK_NAME", "GUSUNG_1", "GUSUNG_2", "GUSUNG_3",
        "GUSUNG_4", "GUSUNG_5", "GUSUNG_6"
      ]),
      storageShape: "six quarter_integer columns",
      deleteAllConfirmation: "DELETE_ALL_INPUT_PROFIT"
    })
  });

  const sourceContract = Object.freeze({
    originalForm: "Form/GraphE.cs",
    provider: "oracle",
    operatorSchemaVersion: OPERATOR_SCHEMA_VERSION,
    restoreRequiresNativeLoad: true,
    rawNamedPlaylistRowPlayable: false,
    screens: Object.freeze(Object.values(screenDefinitions)),
    writeSafety: Object.freeze({
      valuesBound: true,
      oneTransactionPerWrite: true,
      exclusiveTableIdentityCheck: true,
      optimisticRowVersion: "SHA-256",
      outcomeUnknownAutomaticRetry: false
    })
  });

  const actions = Object.freeze(Object.values(screenDefinitions).map(definition => Object.freeze({
    id: definition.screen,
    label: definition.label,
    screen: definition.screen,
    builderKey: definition.builderKey,
    code: definition.code,
    aliases: definition.aliases,
    pageSize: 1
  })));

  const trustedSnapshots = new WeakSet();
  const trustedStockSearchResponses = new WeakSet();
  const verifiedStocks = new WeakSet();
  const trustedSelectionResponses = new WeakSet();
  const trustedPlaylistEntries = new WeakSet();
  const pendingRestoreEntries = new WeakSet();

  function hasExactKeys(value, keys) {
    if (!value || typeof value !== "object" || Array.isArray(value)) return false;
    const actual = Object.keys(value).sort();
    const expected = [...keys].sort();
    return actual.length === expected.length &&
      actual.every((key, index) => key === expected[index]);
  }

  function isSafeText(value, maximumLength, options = {}) {
    const { allowEmpty = false, allowUnderscore = true } = options;
    return typeof value === "string" && value.length <= maximumLength &&
      (allowEmpty || value.length > 0) && value === value.trim() &&
      !unsafeTextPattern.test(value) && !value.includes("^") &&
      (allowUnderscore || !value.includes("_"));
  }

  function isRequestId(value) {
    return typeof value === "string" && requestIdPattern.test(value);
  }

  function definition(screen) {
    return typeof screen === "string" &&
      Object.prototype.hasOwnProperty.call(screenDefinitions, screen)
      ? screenDefinitions[screen]
      : null;
  }

  function normalizeQuery(value) {
    if (typeof value !== "string" || unsafeTextPattern.test(value)) return null;
    const query = value.trim();
    return query.length <= MAXIMUM_QUERY_LENGTH && !unsafeTextPattern.test(query)
      ? query
      : null;
  }

  function normalizeFinite(value) {
    return typeof value === "number" && Number.isFinite(value) &&
      Math.abs(value) <= MAXIMUM_FLOAT ? value : null;
  }

  function normalizeRevenueSlice(value) {
    if (value === null) return null;
    if (!hasExactKeys(value, ["label", "percentageText", "percentage"]) ||
        !isSafeText(value.label, 200, { allowUnderscore: false }) ||
        !isSafeText(value.percentageText, 64, { allowUnderscore: false }) ||
        normalizeFinite(value.percentage) === null) return undefined;
    const parsed = value.percentageText === "-"
      ? 0
      : numberTextPattern.test(value.percentageText)
        ? Number(value.percentageText)
        : Number.NaN;
    if (!Number.isFinite(parsed) || Math.abs(parsed) > MAXIMUM_FLOAT ||
        !Object.is(parsed, value.percentage) && parsed !== value.percentage) return undefined;
    return Object.freeze({
      label: value.label,
      percentageText: value.percentageText,
      percentage: parsed
    });
  }

  function normalizeGrowthSeries(value) {
    if (!Array.isArray(value) || value.length !== 4) return null;
    const result = [];
    for (const item of value) {
      if (item === null) {
        result.push(null);
      } else {
        const normalized = normalizeFinite(item);
        if (normalized === null) return null;
        result.push(normalized);
      }
    }
    return Object.freeze(result);
  }

  function normalizePeriods(value) {
    if (!Array.isArray(value) || value.length !== 4) return null;
    const periods = [];
    for (const period of value) {
      if (!isSafeText(period, 200, { allowEmpty: true, allowUnderscore: false })) return null;
      periods.push(period);
    }
    return Object.freeze(periods);
  }

  function normalizeQuarters(value) {
    if (!Array.isArray(value) || value.length !== 6) return null;
    const quarters = [];
    for (const item of value) {
      if (!hasExactKeys(item, ["quarter", "value"]) ||
          !isSafeText(item.quarter, 200, { allowUnderscore: false }) ||
          !Number.isInteger(item.value) || item.value < int32Minimum || item.value > int32Maximum) {
        return null;
      }
      quarters.push(Object.freeze({ quarter: item.quarter, value: item.value }));
    }
    return Object.freeze(quarters);
  }

  function normalizeRecord(screen, value) {
    const profile = definition(screen);
    if (!profile || !value || typeof value !== "object" || Array.isArray(value) ||
        value.kind !== screen || !isSafeText(value.stockName, 200)) return null;

    if (screen === "revenue-composition") {
      if (!hasExactKeys(value, ["kind", "stockName", "baseDate", "slices"]) ||
          !isSafeText(value.baseDate, 200, { allowEmpty: true }) ||
          !Array.isArray(value.slices) || value.slices.length !== 5) return null;
      const slices = [];
      for (const raw of value.slices) {
        const slice = normalizeRevenueSlice(raw);
        if (slice === undefined) return null;
        slices.push(slice);
      }
      return Object.freeze({
        kind: screen,
        stockName: value.stockName,
        baseDate: value.baseDate,
        slices: Object.freeze(slices)
      });
    }

    if (screen === "growth-metrics") {
      if (!hasExactKeys(value, [
        "kind", "stockName", "salesGrowth", "operatingProfitGrowth",
        "netAssetGrowth", "netIncomeGrowth", "periods"
      ])) return null;
      const salesGrowth = normalizeGrowthSeries(value.salesGrowth);
      const operatingProfitGrowth = normalizeGrowthSeries(value.operatingProfitGrowth);
      const netAssetGrowth = normalizeGrowthSeries(value.netAssetGrowth);
      const netIncomeGrowth = normalizeGrowthSeries(value.netIncomeGrowth);
      const periods = normalizePeriods(value.periods);
      if (!salesGrowth || !operatingProfitGrowth || !netAssetGrowth ||
          !netIncomeGrowth || !periods) return null;
      return Object.freeze({
        kind: screen,
        stockName: value.stockName,
        salesGrowth,
        operatingProfitGrowth,
        netAssetGrowth,
        netIncomeGrowth,
        periods
      });
    }

    if (screen === "sales" || screen === "operating-profit") {
      if (!hasExactKeys(value, ["kind", "stockName", "quarters"])) return null;
      const quarters = normalizeQuarters(value.quarters);
      return quarters && Object.freeze({
        kind: screen,
        stockName: value.stockName,
        quarters
      });
    }

    return null;
  }

  function serializeRecordForStorage(screen, value) {
    const record = normalizeRecord(screen, value);
    const profile = definition(screen);
    if (!record || !profile) return null;
    let values;
    if (screen === "revenue-composition") {
      values = [record.stockName, record.baseDate,
        ...record.slices.map(slice => slice ? `${slice.label}_${slice.percentageText}` : "")];
    } else if (screen === "growth-metrics") {
      const series = [record.salesGrowth, record.operatingProfitGrowth,
        record.netAssetGrowth, record.netIncomeGrowth];
      values = [record.stockName,
        ...series.map(items => items.map(item => item === null ? "" : String(item)).join("_")),
        record.periods.join("_")];
    } else {
      values = [record.stockName,
        ...record.quarters.map(item => `${item.quarter}_${item.value}`)];
    }
    return Object.freeze({
      table: profile.table,
      columns: profile.storageColumns,
      values: Object.freeze(values)
    });
  }

  function createListRequest(requestId, screen, query = "", maximumResults) {
    if (!isRequestId(requestId)) throw new TypeError("A safe list request id is required.");
    if (!definition(screen)) throw new TypeError("A closed manual-financial screen is required.");
    const normalizedQuery = normalizeQuery(query);
    if (normalizedQuery === null) throw new TypeError("The manual-financial query is invalid.");
    if (maximumResults !== undefined &&
        (!Number.isInteger(maximumResults) || maximumResults < 1 || maximumResults > MAXIMUM_RESULTS)) {
      throw new RangeError(`Manual-financial results must be limited from 1 through ${MAXIMUM_RESULTS}.`);
    }
    const request = { requestId, screen, query: normalizedQuery };
    if (maximumResults !== undefined) request.maximumResults = maximumResults;
    return Object.freeze(request);
  }

  function normalizeRequestError(value, expectedOperation) {
    if (!hasExactKeys(value, ["requestId", "operation", "code", "message"]) ||
        value.requestId !== "" && !isRequestId(value.requestId) ||
        ![
          "list", "load", "selection", "create", "update", "delete-one", "delete-all"
        ].includes(value.operation) || value.code !== "INVALID_REQUEST" ||
        !isSafeText(value.message, 512) ||
        expectedOperation !== undefined && value.operation !== expectedOperation) return null;
    return Object.freeze({ ...value });
  }

  function compareNames(left, right) {
    const foldedLeft = left.toUpperCase();
    const foldedRight = right.toUpperCase();
    if (foldedLeft < foldedRight) return -1;
    if (foldedLeft > foldedRight) return 1;
    if (left < right) return -1;
    if (left > right) return 1;
    return 0;
  }

  function normalizeSnapshot(screen, value) {
    if (!hasExactKeys(value, ["stockName", "rowVersion", "record"]) ||
        !rowVersionPattern.test(value.rowVersion)) return null;
    const record = normalizeRecord(screen, value.record);
    if (!record || value.stockName !== record.stockName) return null;
    const snapshot = Object.freeze({
      screen,
      stockName: record.stockName,
      rowVersion: value.rowVersion,
      record
    });
    trustedSnapshots.add(snapshot);
    return snapshot;
  }

  function normalizeListResponse(value, expectedRequest) {
    if (!hasExactKeys(value, [
      "requestId", "screen", "query", "retrievedAt", "totalRowCount", "truncated", "items"
    ]) || !isRequestId(value.requestId) || !definition(value.screen) ||
        normalizeQuery(value.query) !== value.query ||
        !isSafeText(value.retrievedAt, 64) || !Number.isFinite(Date.parse(value.retrievedAt)) ||
        !Number.isInteger(value.totalRowCount) || value.totalRowCount < 0 ||
        value.totalRowCount > MAXIMUM_RESULTS || typeof value.truncated !== "boolean" ||
        !Array.isArray(value.items) || value.items.length !== value.totalRowCount) return null;

    if (expectedRequest !== undefined) {
      let expected;
      try {
        expected = createListRequest(
          expectedRequest?.requestId,
          expectedRequest?.screen,
          expectedRequest?.query,
          expectedRequest?.maximumResults);
      } catch {
        return null;
      }
      const bound = expected.maximumResults ?? DEFAULT_MAXIMUM_RESULTS;
      if (value.requestId !== expected.requestId || value.screen !== expected.screen ||
          value.query !== expected.query || value.items.length > bound ||
          value.truncated && value.items.length !== bound) return null;
    }

    const items = [];
    const names = new Set();
    for (const raw of value.items) {
      const snapshot = normalizeSnapshot(value.screen, raw);
      const foldedName = snapshot?.stockName.toUpperCase();
      if (!snapshot || names.has(foldedName) ||
          items.length && compareNames(items[items.length - 1].stockName, snapshot.stockName) > 0) {
        return null;
      }
      names.add(foldedName);
      items.push(snapshot);
    }
    return Object.freeze({
      requestId: value.requestId,
      screen: value.screen,
      query: value.query,
      retrievedAt: value.retrievedAt,
      totalRowCount: value.totalRowCount,
      truncated: value.truncated,
      items: Object.freeze(items)
    });
  }

  function normalizeListError(value, expectedRequest) {
    if (!hasExactKeys(value, ["requestId", "screen", "query", "message"]) ||
        !isRequestId(value.requestId) || !definition(value.screen) ||
        normalizeQuery(value.query) !== value.query ||
        !isSafeText(value.message, 512)) return null;
    if (expectedRequest) {
      let expected;
      try {
        expected = createListRequest(
          expectedRequest.requestId,
          expectedRequest.screen,
          expectedRequest.query,
          expectedRequest.maximumResults);
      } catch {
        return null;
      }
      if (value.requestId !== expected.requestId || value.screen !== expected.screen ||
          value.query !== expected.query) return null;
    }
    return Object.freeze({
      requestId: value.requestId,
      screen: value.screen,
      query: value.query,
      message: value.message
    });
  }

  function createLoadRequest(requestId, screen, stockName) {
    if (!isRequestId(requestId)) throw new TypeError("A safe load request id is required.");
    if (!definition(screen) || !isSafeText(stockName, 200)) {
      throw new TypeError("A closed screen and canonical stock name are required.");
    }
    return Object.freeze({ requestId, screen, stockName });
  }

  function normalizeLoadResponse(value, expectedRequest) {
    if (!hasExactKeys(value, ["requestId", "screen", "retrievedAt", "snapshot"]) ||
        !isRequestId(value.requestId) || !definition(value.screen) ||
        !isSafeText(value.retrievedAt, 64) || !Number.isFinite(Date.parse(value.retrievedAt))) return null;
    let expected;
    if (expectedRequest !== undefined) {
      try {
        expected = createLoadRequest(
          expectedRequest?.requestId,
          expectedRequest?.screen,
          expectedRequest?.stockName);
      } catch {
        return null;
      }
      if (value.requestId !== expected.requestId || value.screen !== expected.screen) return null;
    }
    const snapshot = normalizeSnapshot(value.screen, value.snapshot);
    if (!snapshot || expected && snapshot.stockName !== expected.stockName) return null;
    return Object.freeze({
      requestId: value.requestId,
      screen: value.screen,
      retrievedAt: value.retrievedAt,
      snapshot
    });
  }

  function normalizeLoadError(value, expectedRequest) {
    if (!hasExactKeys(value, [
      "requestId", "screen", "stockName", "code", "message", "retryable"
    ]) || !isRequestId(value.requestId) || !definition(value.screen) ||
        !isSafeText(value.stockName, 200) || !isSafeText(value.code, 64) ||
        !isSafeText(value.message, 512) || typeof value.retryable !== "boolean") return null;
    if (expectedRequest) {
      let expected;
      try {
        expected = createLoadRequest(
          expectedRequest.requestId,
          expectedRequest.screen,
          expectedRequest.stockName);
      } catch {
        return null;
      }
      if (value.requestId !== expected.requestId || value.screen !== expected.screen ||
          value.stockName !== expected.stockName) return null;
    }
    return Object.freeze({ ...value });
  }

  function normalizeStockSearchResponse(value) {
    if (!hasExactKeys(value, [
      "requestId", "query", "retrievedAt", "totalRowCount", "truncated", "results"
    ]) || !isRequestId(value.requestId) || normalizeQuery(value.query) !== value.query ||
        !isSafeText(value.retrievedAt, 64) || !Number.isFinite(Date.parse(value.retrievedAt)) ||
        !Number.isInteger(value.totalRowCount) || value.totalRowCount < 0 ||
        typeof value.truncated !== "boolean" || !Array.isArray(value.results) ||
        value.results.length !== value.totalRowCount) return null;
    const results = [];
    const identities = new Set();
    for (const item of value.results) {
      if (!hasExactKeys(item, ["market", "source", "name", "code"]) ||
          !["kospi", "kosdaq", "nxt-kospi", "nxt-kosdaq"].includes(item.market) ||
          !["oracle", "mariaDb"].includes(item.source) ||
          (item.market.startsWith("nxt-") ? item.source !== "mariaDb" : item.source !== "oracle") ||
          !isSafeText(item.name, 200) || !stockCodePattern.test(item.code)) return null;
      const key = `${item.market}\u001f${item.source}\u001f${item.code}`;
      if (identities.has(key)) return null;
      identities.add(key);
      results.push(Object.freeze({
        market: item.market,
        source: item.source,
        name: item.name,
        code: item.code
      }));
    }
    const response = Object.freeze({
      requestId: value.requestId,
      query: value.query.trim(),
      retrievedAt: value.retrievedAt,
      totalRowCount: value.totalRowCount,
      truncated: value.truncated,
      results: Object.freeze(results)
    });
    trustedStockSearchResponses.add(response);
    return response;
  }

  function verifyStockForRecord(recordValue, stockResponse, selectedMarket, selectedCode) {
    const screen = recordValue?.kind;
    const record = normalizeRecord(screen, recordValue);
    if (!record || !stockResponse || !trustedStockSearchResponses.has(stockResponse) ||
        typeof selectedMarket !== "string" || !stockCodePattern.test(selectedCode)) return null;
    const matches = stockResponse.results.filter(item => item.name === record.stockName);
    if (matches.length !== 1 || matches[0].market !== selectedMarket ||
        matches[0].code !== selectedCode) return null;
    const verified = Object.freeze({
      market: matches[0].market,
      source: matches[0].source,
      name: matches[0].name,
      code: matches[0].code
    });
    verifiedStocks.add(verified);
    return verified;
  }

  function normalizeVerifiedStockValue(value) {
    if (!hasExactKeys(value, ["market", "source", "name", "code"]) ||
        !["kospi", "kosdaq", "nxt-kospi", "nxt-kosdaq"].includes(value.market) ||
        !["oracle", "mariaDb"].includes(value.source) ||
        (value.market.startsWith("nxt-") ? value.source !== "mariaDb" : value.source !== "oracle") ||
        !isSafeText(value.name, 200) || !stockCodePattern.test(value.code)) return null;
    return Object.freeze({
      market: value.market,
      source: value.source,
      name: value.name,
      code: value.code
    });
  }

  function createSelectionRequest(requestId, snapshot, verifiedStock) {
    if (!isRequestId(requestId)) throw new TypeError("A safe selection request id is required.");
    if (!snapshot || !trustedSnapshots.has(snapshot) ||
        !verifiedStock || !verifiedStocks.has(verifiedStock) ||
        snapshot.stockName !== verifiedStock.name) {
      throw new TypeError("Selection requires a native snapshot and freshly verified stock.");
    }
    return Object.freeze({
      requestId,
      screen: snapshot.screen,
      stockName: snapshot.stockName,
      rowVersion: snapshot.rowVersion,
      stock: Object.freeze({
        market: verifiedStock.market,
        source: verifiedStock.source,
        name: verifiedStock.name,
        code: verifiedStock.code
      })
    });
  }

  function normalizeSelectionResponse(value, expectedRequest) {
    if (!hasExactKeys(value, [
      "requestId", "screen", "stockName", "rowVersion", "builderKey", "code",
      "aliases", "selection", "currentPage", "totalPages"
    ]) || !isRequestId(value.requestId) || !definition(value.screen) ||
        !isSafeText(value.stockName, 200) || !rowVersionPattern.test(value.rowVersion) ||
        !isSafeText(value.builderKey, 32) || !/^[0-9A-Z]{4,6}$/.test(value.code) ||
        !Array.isArray(value.aliases) || value.aliases.length !== 1 ||
        value.aliases[0] !== value.code ||
        !hasExactKeys(value.selection, [
          "groupCode", "subject", "graphicType", "subtype", "dataCode"
        ]) || value.currentPage !== 1 || value.totalPages !== 1) return null;
    const expected = expectedRequest;
    if (!expected || !hasExactKeys(expected, [
      "requestId", "screen", "stockName", "rowVersion", "stock"
    ]) || value.requestId !== expected.requestId || value.screen !== expected.screen ||
        value.stockName !== expected.stockName || value.rowVersion !== expected.rowVersion ||
        !normalizeVerifiedStockValue(expected.stock) || expected.stock.name !== expected.stockName) return null;
    const profile = definition(value.screen);
    const groupCode = {
      kospi: "KOSPI",
      kosdaq: "KOSDAQ",
      "nxt-kospi": "NXT_KOSPI",
      "nxt-kosdaq": "NXT_KOSDAQ"
    }[expected.stock?.market];
    if (!profile || !groupCode || value.builderKey !== profile.builderKey ||
        value.code !== profile.code ||
        value.selection.groupCode !== groupCode ||
        value.selection.subject !== value.stockName ||
        value.selection.graphicType !== profile.graphicType ||
        value.selection.subtype !== "" || value.selection.dataCode !== "") return null;
    const normalized = Object.freeze({
      requestId: value.requestId,
      screen: value.screen,
      stockName: value.stockName,
      rowVersion: value.rowVersion,
      builderKey: value.builderKey,
      code: value.code,
      aliases: Object.freeze([...value.aliases]),
      selection: Object.freeze({ ...value.selection }),
      currentPage: 1,
      totalPages: 1
    });
    trustedSelectionResponses.add(normalized);
    return normalized;
  }

  function normalizeSelectionError(value, expectedRequest) {
    if (!hasExactKeys(value, [
      "requestId", "screen", "stockName", "code", "message", "retryable"
    ]) || !isRequestId(value.requestId) || !definition(value.screen) ||
        !isSafeText(value.stockName, 200) || !isSafeText(value.code, 64) ||
        !isSafeText(value.message, 512) || typeof value.retryable !== "boolean") return null;
    if (expectedRequest &&
        (value.requestId !== expectedRequest.requestId || value.screen !== expectedRequest.screen ||
         value.stockName !== expectedRequest.stockName)) return null;
    return Object.freeze({ ...value });
  }

  function createCreateRequest(requestId, recordValue, verifiedStock) {
    const screen = recordValue?.kind;
    const record = normalizeRecord(screen, recordValue);
    if (!isRequestId(requestId)) throw new TypeError("A safe create request id is required.");
    if (!record || !verifiedStock || !verifiedStocks.has(verifiedStock) ||
        verifiedStock.name !== record.stockName) {
      throw new TypeError("Create requires one verified stock and a valid closed record.");
    }
    return Object.freeze({
      requestId,
      screen,
      stock: Object.freeze({
        market: verifiedStock.market,
        source: verifiedStock.source,
        name: verifiedStock.name,
        code: verifiedStock.code
      }),
      record
    });
  }

  function createUpdateRequest(requestId, snapshot, replacementValue) {
    if (!isRequestId(requestId)) throw new TypeError("A safe update request id is required.");
    if (!snapshot || !trustedSnapshots.has(snapshot)) {
      throw new TypeError("Update requires a native-normalized snapshot.");
    }
    const replacement = normalizeRecord(snapshot.screen, replacementValue);
    if (!replacement || replacement.stockName !== snapshot.stockName) {
      throw new TypeError("A GraphE update cannot rename a stock or change its screen.");
    }
    return Object.freeze({
      requestId,
      screen: snapshot.screen,
      stockName: snapshot.stockName,
      expectedRowVersion: snapshot.rowVersion,
      record: replacement
    });
  }

  function createDeleteRequest(requestId, snapshot) {
    if (!isRequestId(requestId)) throw new TypeError("A safe delete request id is required.");
    if (!snapshot || !trustedSnapshots.has(snapshot)) {
      throw new TypeError("Delete requires a native-normalized snapshot.");
    }
    return Object.freeze({
      requestId,
      screen: snapshot.screen,
      stockName: snapshot.stockName,
      expectedRowVersion: snapshot.rowVersion
    });
  }

  function createDeleteAllRequest(requestId, screen, confirmationToken) {
    const profile = definition(screen);
    if (!isRequestId(requestId)) throw new TypeError("A safe delete-all request id is required.");
    if (!profile || confirmationToken !== profile.deleteAllConfirmation) {
      throw new TypeError("Delete-all requires the exact screen confirmation token.");
    }
    return Object.freeze({ requestId, screen, confirmationToken });
  }

  function normalizeMutationResult(value, expectedRequest) {
    if (!hasExactKeys(value, [
      "requestId", "operationId", "operation", "screen", "stockName", "committedAt", "affectedRows"
    ]) || !isRequestId(value.requestId) || !isRequestId(value.operationId) ||
        !["create", "update", "delete-one", "delete-all"].includes(value.operation) ||
        !definition(value.screen) ||
        typeof value.stockName !== "string" ||
        (value.operation === "delete-all"
          ? value.stockName !== ""
          : !isSafeText(value.stockName, 200)) ||
        !isSafeText(value.committedAt, 64) || !Number.isFinite(Date.parse(value.committedAt)) ||
        !Number.isInteger(value.affectedRows) || value.affectedRows < 0 ||
        value.operation !== "delete-all" && value.affectedRows !== 1) return null;
    if (expectedRequest &&
        (value.requestId !== expectedRequest.requestId || value.screen !== expectedRequest.screen ||
         value.operation !== operationForRequest(expectedRequest) ||
         value.stockName !== (expectedRequest.stockName ?? expectedRequest.record?.stockName ?? ""))) return null;
    return Object.freeze({ ...value });
  }

  function normalizeMutationError(value, expectedRequest) {
    if (!hasExactKeys(value, [
      "requestId", "operation", "screen", "stockName", "code", "message",
      "retryable", "outcomeUnknown"
    ]) || !isRequestId(value.requestId) ||
        !["create", "update", "delete-one", "delete-all"].includes(value.operation) ||
        !definition(value.screen) || typeof value.stockName !== "string" ||
        (value.operation === "delete-all"
          ? value.stockName !== ""
          : !isSafeText(value.stockName, 200)) ||
        !isSafeText(value.code, 64) || !isSafeText(value.message, 512) ||
        value.retryable !== false || typeof value.outcomeUnknown !== "boolean") return null;
    if (expectedRequest &&
        (value.requestId !== expectedRequest.requestId || value.screen !== expectedRequest.screen ||
         value.operation !== operationForRequest(expectedRequest) ||
         value.stockName !== (expectedRequest.stockName ?? expectedRequest.record?.stockName ?? ""))) return null;
    return Object.freeze({ ...value });
  }

  function operationForRequest(value) {
    if (!value || typeof value !== "object" || Array.isArray(value)) return null;
    if (Object.prototype.hasOwnProperty.call(value, "confirmationToken")) return "delete-all";
    if (Object.prototype.hasOwnProperty.call(value, "expectedRowVersion")) {
      return Object.prototype.hasOwnProperty.call(value, "record") ? "update" : "delete-one";
    }
    return Object.prototype.hasOwnProperty.call(value, "record") ? "create" : null;
  }

  function selectionForValues(screen, stockName, stock) {
    const profile = definition(screen);
    const groupCode = {
      kospi: "KOSPI",
      kosdaq: "KOSDAQ",
      "nxt-kospi": "NXT_KOSPI",
      "nxt-kosdaq": "NXT_KOSDAQ"
    }[stock?.market];
    if (!profile || !groupCode) return null;
    return Object.freeze({
      builderKey: profile.builderKey,
      code: profile.code,
      aliases: profile.aliases,
      selection: Object.freeze({
        groupCode,
        subject: stockName,
        graphicType: profile.graphicType,
        subtype: "",
        dataCode: ""
      }),
      currentPage: 1,
      pageCount: 1
    });
  }

  function buildSelection(snapshot, verifiedStock) {
    if (!snapshot || !trustedSnapshots.has(snapshot) ||
        !verifiedStock || !verifiedStocks.has(verifiedStock) ||
        snapshot.stockName !== verifiedStock.name) return null;
    return selectionForValues(snapshot.screen, snapshot.stockName, verifiedStock);
  }

  function normalizePlaylistOptions(options, profile, stock) {
    if (!options || typeof options !== "object" || Array.isArray(options) ||
        !Object.keys(options).every(key => ["id", "fadeDuration", "enabled"].includes(key))) {
      throw new TypeError("Manual-financial playlist options are invalid.");
    }
    const id = options.id ?? `manual-financial-${profile.code}-${stock.code}`;
    const fadeDuration = options.fadeDuration ?? DEFAULT_FADE_DURATION;
    const enabled = options.enabled ?? true;
    if (!isRequestId(id) || !Number.isInteger(fadeDuration) ||
        fadeDuration < 0 || fadeDuration > 60 || typeof enabled !== "boolean") {
      throw new TypeError("Manual-financial playlist options are invalid.");
    }
    return Object.freeze({ id, fadeDuration, enabled });
  }

  function createEntryObject(snapshot, stock, selection, options) {
    const profile = definition(snapshot.screen);
    const normalizedOptions = normalizePlaylistOptions(options, profile, stock);
    const snapshotMetadata = Object.freeze({
      screen: snapshot.screen,
      stockName: snapshot.stockName,
      rowVersion: snapshot.rowVersion,
      record: snapshot.record
    });
    const stockMetadata = Object.freeze({
      market: stock.market,
      source: stock.source,
      name: stock.name,
      code: stock.code
    });
    return Object.freeze({
      id: normalizedOptions.id,
      builderKey: selection.builderKey,
      code: selection.code,
      aliases: Object.freeze([...selection.aliases]),
      title: snapshot.stockName,
      detail: profile.label,
      category: "manual-financial",
      market: stock.market,
      stockName: snapshot.stockName,
      stockCode: stock.code,
      isNxt: stock.market.startsWith("nxt-"),
      selection: Object.freeze({ ...selection.selection }),
      enabled: normalizedOptions.enabled,
      fadeDuration: normalizedOptions.fadeDuration,
      graphicType: selection.selection.graphicType,
      subtype: selection.selection.subtype,
      currentPage: 1,
      pageCount: 1,
      operator: Object.freeze({
        source: "manual-financial-workflow",
        schemaVersion: OPERATOR_SCHEMA_VERSION,
        screen: snapshot.screen,
        snapshot: snapshotMetadata,
        verifiedStock: stockMetadata
      })
    });
  }

  function createPlaylistEntry(snapshot, verifiedStock, selectionResponse, options = {}) {
    const selection = buildSelection(snapshot, verifiedStock);
    if (!selection || !selectionResponse ||
        !trustedSelectionResponses.has(selectionResponse) ||
        selectionResponse.screen !== snapshot.screen ||
        selectionResponse.stockName !== snapshot.stockName ||
        selectionResponse.rowVersion !== snapshot.rowVersion ||
        selectionResponse.builderKey !== selection.builderKey ||
        selectionResponse.code !== selection.code ||
        !deepEqualValue(selectionResponse.aliases, selection.aliases) ||
        !deepEqualValue(selectionResponse.selection, selection.selection) ||
        selectionResponse.currentPage !== 1 || selectionResponse.totalPages !== 1) {
      throw new TypeError(
        "A native-confirmed selection, trusted snapshot and verified stock are required.");
    }
    const entry = createEntryObject(snapshot, verifiedStock, selection, options);
    trustedPlaylistEntries.add(entry);
    return entry;
  }

  function deepEqualValue(left, right) {
    if (Object.is(left, right)) return true;
    if (Array.isArray(left) || Array.isArray(right)) {
      return Array.isArray(left) && Array.isArray(right) &&
        left.length === right.length &&
        left.every((value, index) => deepEqualValue(value, right[index]));
    }
    if (!left || typeof left !== "object" || !right || typeof right !== "object") return false;
    const leftKeys = Object.keys(left).sort();
    const rightKeys = Object.keys(right).sort();
    return leftKeys.length === rightKeys.length &&
      leftKeys.every((key, index) => key === rightKeys[index] &&
        deepEqualValue(left[key], right[key]));
  }

  function restorePlaylistEntry(value) {
    if (!hasExactKeys(value, [
      "id", "builderKey", "code", "aliases", "title", "detail", "category", "market",
      "stockName", "stockCode", "isNxt", "selection", "enabled", "fadeDuration",
      "graphicType", "subtype", "currentPage", "pageCount", "operator"
    ]) || !value.operator || !hasExactKeys(value.operator, [
      "source", "schemaVersion", "screen", "snapshot", "verifiedStock"
    ]) || value.operator.source !== "manual-financial-workflow" ||
        value.operator.schemaVersion !== OPERATOR_SCHEMA_VERSION ||
        !definition(value.operator.screen) ||
        !hasExactKeys(value.operator.snapshot, ["screen", "stockName", "rowVersion", "record"]) ||
        value.operator.snapshot.screen !== value.operator.screen ||
        !rowVersionPattern.test(value.operator.snapshot.rowVersion)) return null;
    const record = normalizeRecord(value.operator.screen, value.operator.snapshot.record);
    const stock = normalizeVerifiedStockValue(value.operator.verifiedStock);
    if (!record || !stock || record.stockName !== value.operator.snapshot.stockName ||
        stock.name !== record.stockName) return null;
    const snapshot = Object.freeze({
      screen: value.operator.screen,
      stockName: record.stockName,
      rowVersion: value.operator.snapshot.rowVersion,
      record
    });
    const selection = selectionForValues(snapshot.screen, snapshot.stockName, stock);
    if (!selection) return null;
    let canonical;
    try {
      canonical = createEntryObject(snapshot, stock, selection, {
        id: value.id,
        fadeDuration: value.fadeDuration,
        enabled: value.enabled
      });
    } catch {
      return null;
    }
    if (!deepEqualValue(value, canonical)) return null;
    const pending = Object.freeze({
      status: "refresh-required",
      playable: false,
      entry: canonical,
      identity: Object.freeze({ screen: snapshot.screen, stockName: snapshot.stockName }),
      rowVersion: snapshot.rowVersion,
      verifiedStock: stock
    });
    pendingRestoreEntries.add(pending);
    return pending;
  }

  function createRestoreLoadRequest(requestId, pending) {
    if (!pending || !pendingRestoreEntries.has(pending)) {
      throw new TypeError("A validated pending restore is required.");
    }
    return createLoadRequest(requestId, pending.identity.screen, pending.identity.stockName);
  }

  function sameStoredSnapshot(pending, snapshot) {
    const stored = pending?.entry?.operator?.snapshot;
    return !!stored && snapshot.screen === stored.screen &&
      snapshot.stockName === stored.stockName && snapshot.rowVersion === stored.rowVersion &&
      deepEqualValue(snapshot.record, stored.record);
  }

  function createRestoreSelectionRequest(requestId, pending, loadResponse, verifiedStock) {
    if (!pending || !pendingRestoreEntries.has(pending) ||
        !loadResponse || !loadResponse.snapshot ||
        !trustedSnapshots.has(loadResponse.snapshot) ||
        !verifiedStock || !verifiedStocks.has(verifiedStock) ||
        !sameStoredSnapshot(pending, loadResponse.snapshot) ||
        !deepEqualValue(pending.verifiedStock, verifiedStock)) {
      throw new TypeError("Restore refresh did not reproduce the stored identities.");
    }
    return createSelectionRequest(requestId, loadResponse.snapshot, verifiedStock);
  }

  function materializeRestoredPlaylistEntry(
    pending,
    loadResponse,
    verifiedStock,
    selectionResponse) {
    if (!pending || !pendingRestoreEntries.has(pending) ||
        !loadResponse || !loadResponse.snapshot ||
        !trustedSnapshots.has(loadResponse.snapshot) ||
        !verifiedStock || !verifiedStocks.has(verifiedStock) ||
        !selectionResponse || !trustedSelectionResponses.has(selectionResponse) ||
        !sameStoredSnapshot(pending, loadResponse.snapshot) ||
        !deepEqualValue(pending.verifiedStock, verifiedStock)) return null;
    let recreated;
    try {
      recreated = createPlaylistEntry(
        loadResponse.snapshot,
        verifiedStock,
        selectionResponse,
        {
          id: pending.entry.id,
          fadeDuration: pending.entry.fadeDuration,
          enabled: pending.entry.enabled
        });
    } catch {
      return null;
    }
    return deepEqualValue(recreated, pending.entry) ? recreated : null;
  }

  function isPlaylistEntryPlayable(value) {
    return !!value && trustedPlaylistEntries.has(value);
  }

  function withEnabled(entry, enabled) {
    if (!trustedPlaylistEntries.has(entry) || typeof enabled !== "boolean") return null;
    if (entry.enabled === enabled) return entry;
    const replacement = Object.freeze({ ...entry, enabled });
    trustedPlaylistEntries.add(replacement);
    return replacement;
  }

  return {
    bridgeContract,
    sourceContract,
    actions,
    getScreenDefinition: definition,
    normalizeRecord,
    serializeRecordForStorage,
    normalizeRequestError,
    createListRequest,
    normalizeListResponse,
    normalizeListError,
    createLoadRequest,
    normalizeLoadResponse,
    normalizeLoadError,
    normalizeStockSearchResponse,
    verifyStockForRecord,
    createSelectionRequest,
    normalizeSelectionResponse,
    normalizeSelectionError,
    createCreateRequest,
    createUpdateRequest,
    createDeleteRequest,
    createDeleteAllRequest,
    normalizeMutationResult,
    normalizeMutationError,
    buildSelection,
    createPlaylistEntry,
    restorePlaylistEntry,
    createRestoreLoadRequest,
    createRestoreSelectionRequest,
    materializeRestoredPlaylistEntry,
    withEnabled,
    isPlaylistEntryPlayable
  };
});
