import { CATALOGUE, DISCLOSURE, DOCUMENTS } from './fixtures.mjs';
import { SCENARIOS } from './scenario-data.mjs';
export { DISCLOSURE };
const stores = new WeakMap();
const copy = value => JSON.parse(JSON.stringify(value));
function state(workflow) {
  const s = stores.get(workflow);
  if (!s) throw new Error('UNKNOWN_WORKFLOW');
  return s;
}
export function createWorkflow({ catalogue = CATALOGUE } = {}) {
  const handle = Object.freeze({});
  stores.set(handle, { catalogue: copy(catalogue), drafts: [], documents: [], audit: [], downstream: { attempts: [], records: [] } });
  return handle;
}
const labels = { 'Request-ID': 'requestId', 'Order-date': 'orderDate', 'Delivery-date': 'deliveryDate', Item: 'item', Quantity: 'quantity' };
function extract(document, catalogue) {
  const fields = Object.fromEntries(Object.values(labels).map(key => [key, { value: null, sources: [], observedValues: [] }]));
  const ignoredLines = [];
  for (const [i, text] of document.text.split(/\r?\n/).entries()) {
    const match = /^([A-Za-z-]+):\s*(.*)$/.exec(text);
    const key = match && Object.hasOwn(labels, match[1]) ? labels[match[1]] : null;
    const source = { documentId: document.id, line: i + 1, text };
    if (!key) { if (text.trim()) ignoredLines.push(source); continue; }
    const value = key === 'quantity' ? (/^[1-9]\d*$/.test(match[2]) ? Number(match[2]) : null) : match[2].trim();
    fields[key].sources.push(source);
    fields[key].observedValues.push(value);
    fields[key].value = new Set(fields[key].observedValues).size === 1 ? value : null;
  }
  const item = fields.item.value?.toLowerCase();
  const candidates = catalogue.filter(entry => entry.sku.toLowerCase() === item || entry.name.toLowerCase() === item || entry.aliases.includes(item));
  fields.sku = { value: candidates.length === 1 ? candidates[0].sku : null, sources: copy(fields.item.sources), candidates: copy(candidates), catalogueReferences: candidates.map(c => `synthetic-catalogue-v1:${c.sku}`) };
  return { fields, ignoredLines };
}
function validDate(value) {
  if (typeof value !== 'string' || !/^\d{4}-\d{2}-\d{2}$/.test(value)) return false;
  const parsed = new Date(`${value}T00:00:00Z`);
  return Number.isFinite(parsed.getTime()) && parsed.toISOString().slice(0, 10) === value;
}
function validQuantity(value) { return Number.isSafeInteger(value) && value > 0; }
function validate(draft) {
  const exceptions = [];
  if (draft.duplicateOf) exceptions.push({ code: 'DUPLICATE_CONFLICT', field: 'requestId', message: `Different content reuses request identity from ${draft.duplicateOf}; reject and reconcile outside this demo.` });
  if (!validQuantity(draft.fields.quantity.value)) exceptions.push({ code: 'MISSING_QUANTITY', field: 'quantity', message: 'Supply a positive whole-number quantity; no default is inferred.' });
  for (const [field, proposal] of Object.entries(draft.fields)) {
    if (!proposal.corrected && new Set(proposal.observedValues || []).size > 1) exceptions.push({ code: 'CONTRADICTORY_VALUES', field, message: 'Conflicting source values require a recorded human correction.' });
  }
  if (draft.fields.sku.value === null) exceptions.push({ code: draft.fields.sku.candidates.length > 1 ? 'AMBIGUOUS_ITEM' : 'UNKNOWN_ITEM', field: 'sku', message: 'Select a supported catalogue item explicitly.' });
  if (draft.ignoredLines.length && !draft.ignoredTextAcknowledgement) exceptions.push({ code: 'UNRECOGNISED_TEXT', field: 'document', message: 'Unrecognised lines remain data, not instructions. Review them explicitly.' });
  if (!/^[A-Z0-9][A-Z0-9-]{0,63}$/.test(draft.fields.requestId.value || '')) exceptions.push({ code: 'INVALID_REQUEST_ID', field: 'requestId', message: 'A request identity is required; correct the source and resubmit.' });
  for (const field of ['orderDate', 'deliveryDate']) if (!validDate(draft.fields[field].value)) exceptions.push({ code: 'INVALID_DATE', field, message: 'A real calendar date in YYYY-MM-DD form is required.' });
  if (validDate(draft.fields.orderDate.value) && validDate(draft.fields.deliveryDate.value) && draft.fields.deliveryDate.value < draft.fields.orderDate.value) exceptions.push({ code: 'DATE_ORDER', field: 'deliveryDate', message: 'Delivery cannot precede the order date.' });
  draft.exceptions = exceptions;
  draft.status = exceptions.length ? 'exception' : 'pending_review';
}
export function submitDocument(workflow, document) {
  const s = state(workflow);
  if (!document || typeof document.id !== 'string' || !document.id.trim() || document.id.length > 80 || typeof document.text !== 'string' || !document.text.trim() || document.text.length > 50000) throw new Error('INVALID_DOCUMENT');
  const input = { id: document.id, text: document.text };
  if (s.documents.some(d => d.id === input.id && d.text !== input.text)) throw new Error('DOCUMENT_ID_CONFLICT');
  const extracted = extract(input, s.catalogue);
  const existing = s.drafts.find(d => d.originalText === input.text);
  if (existing) {
    s.documents.push(input);
    audit(s, existing, 'duplicate_suppressed', { documentId: input.id });
    return { draftId: existing.id, duplicate: true };
  }
  const draft = { id: `draft-${s.drafts.length + 1}`, originalText: input.text, revision: 1, status: 'pending_review', fields: extracted.fields, ignoredLines: extracted.ignoredLines, exceptions: [], approval: null, corrections: [] };
  draft.duplicateOf = s.drafts.find(d => d.fields.requestId.value && d.fields.requestId.value === draft.fields.requestId.value)?.id || null;
  validate(draft);
  s.documents.push(input);
  s.drafts.push(draft);
  return { draftId: draft.id, duplicate: false };
}
function getDraft(s, id) {
  const draft = s.drafts.find(d => d.id === id);
  if (!draft) throw new Error('UNKNOWN_DRAFT');
  return draft;
}
function decisionDetails(decision) {
  if (!decision || decision.confirmed !== true || typeof decision.actor !== 'string' || !decision.actor.trim() || typeof decision.reason !== 'string' || !decision.reason.trim()) throw new Error('HUMAN_DECISION_REQUIRED');
  return { actor: decision.actor, reason: decision.reason, confirmed: true };
}
function audit(s, draft, type, detail = {}) {
  s.audit.push({ sequence: s.audit.length + 1, draftId: draft.id, revision: draft.revision, type, ...copy(detail) });
}
function editable(draft) {
  if (['rejected', 'released', 'release_failed', 'outcome_unknown'].includes(draft.status)) throw new Error('DRAFT_LOCKED');
}
export function rejectDraft(workflow, id, decision) {
  const s = state(workflow), d = getDraft(s, id), detail = decisionDetails(decision);
  editable(d);
  d.status = 'rejected';
  d.approval = null;
  audit(s, d, 'rejected', detail);
  return copy(d);
}
export function acknowledgeIgnoredText(workflow, id, decision) {
  const s = state(workflow), d = getDraft(s, id), detail = decisionDetails(decision);
  editable(d);
  d.ignoredTextAcknowledgement = detail;
  d.revision += 1;
  d.approval = null;
  validate(d);
  audit(s, d, 'ignored_text_acknowledged', detail);
  return copy(d);
}
export function correctDraft(workflow, id, patch, decision) {
  const s = state(workflow), d = getDraft(s, id), detail = decisionDetails(decision);
  editable(d);
  if (!patch || typeof patch !== 'object' || Array.isArray(patch) || !Object.keys(patch).length) throw new Error('INVALID_CORRECTION');
  for (const [field, value] of Object.entries(patch)) {
    const valid = field === 'quantity' ? validQuantity(value) : ['orderDate', 'deliveryDate'].includes(field) ? validDate(value) : field === 'sku' && s.catalogue.some(item => item.sku === value);
    if (!valid) throw new Error('INVALID_CORRECTION');
  }
  const changes = {};
  for (const [field, value] of Object.entries(patch)) {
    changes[field] = { before: d.fields[field].value, after: value };
    d.fields[field].value = value;
    d.fields[field].corrected = true;
  }
  d.revision += 1;
  d.approval = null;
  d.corrections.push({ revision: d.revision, ...detail, changes });
  validate(d);
  audit(s, d, 'corrected', { ...detail, changes });
  return copy(d);
}
export function approveDraft(workflow, id, decision) {
  const s = state(workflow), d = getDraft(s, id), detail = decisionDetails(decision);
  editable(d);
  if (d.exceptions.length) throw new Error('UNRESOLVED_EXCEPTIONS');
  d.approval = { ...detail, revision: d.revision };
  d.status = 'approved';
  audit(s, d, 'approved', detail);
  return copy(d);
}
export function releaseDraft(workflow, id, { failAt = null } = {}) {
  if (![null, 'before_commit', 'after_commit'].includes(failAt)) throw new Error('INVALID_FAILURE_MODE');
  const s = state(workflow), d = getDraft(s, id);
  if (!d.approval || d.approval.revision !== d.revision) throw new Error('APPROVAL_REQUIRED');
  const payload = Object.fromEntries(['requestId', 'orderDate', 'deliveryDate', 'sku', 'quantity'].map(field => [field, d.fields[field].value]));
  const prior = s.downstream.records.find(record => record.key === `${payload.requestId}:v${d.revision}`);
  if (prior) {
    d.status = 'released';
    audit(s, d, 'duplicate_release_suppressed', { receiptId: prior.id });
    return { status: 'released', duplicate: true, receipt: copy(prior) };
  }
  const receipt = { id: `simulated-record-${s.downstream.records.length + 1}`, key: `${payload.requestId}:v${d.revision}`, payload };
  if (failAt === 'before_commit') {
    d.status = 'release_failed';
    s.downstream.attempts.push({ draftId: id, key: receipt.key, outcome: d.status });
    audit(s, d, d.status, { key: receipt.key });
    return { status: d.status, key: receipt.key, receipt: null };
  }
  s.downstream.attempts.push({ draftId: id, key: receipt.key, outcome: 'released' });
  s.downstream.records.push(receipt);
  if (failAt === 'after_commit') {
    d.status = 'outcome_unknown';
    s.downstream.attempts.at(-1).outcome = d.status;
    audit(s, d, d.status, { key: receipt.key });
    return { status: d.status, key: receipt.key, receipt: null };
  }
  d.status = 'released';
  audit(s, d, 'released', { receiptId: receipt.id });
  return { status: 'released', duplicate: false, receipt: copy(receipt) };
}
export function listScenarios() {
  return SCENARIOS.map(({ id, title }) => ({ id, title }));
}
export function runScenario(id) {
  const scenario = SCENARIOS.find(s => s.id === id);
  if (!scenario) throw new Error('UNKNOWN_SCENARIO');
  const workflow = createWorkflow();
  const decision = { actor: 'Synthetic review operator', reason: 'Scripted demonstration decision, not a real human sign-off.', confirmed: true };
  let draftId;
  const steps = [];
  for (const instruction of scenario.steps) {
    let result, error = null;
    try {
      switch (instruction.action) {
        case 'submit': result = submitDocument(workflow, DOCUMENTS[instruction.document]); draftId = result.draftId; break;
        case 'approve': result = approveDraft(workflow, draftId, decision); break;
        case 'reject': result = rejectDraft(workflow, draftId, decision); break;
        case 'correct': result = correctDraft(workflow, draftId, instruction.patch, decision); break;
        case 'acknowledge': result = acknowledgeIgnoredText(workflow, draftId, decision); break;
        case 'release': result = releaseDraft(workflow, draftId, { failAt: instruction.failAt || null }); break;
        default: throw new Error('UNKNOWN_ACTION');
      }
    } catch (caught) { error = caught.message; }
    if (error !== (instruction.expectedError || null)) throw new Error(`SCENARIO_EXPECTATION_FAILED:${id}:${instruction.action}:${error}`);
    steps.push({ action: instruction.action, instruction: copy(instruction), result: result ?? null, error, snapshot: getSnapshot(workflow) });
  }
  return { id, title: scenario.title, disclosure: DISCLOSURE, scriptedDecisions: true, steps, final: getSnapshot(workflow) };
}
export function getSnapshot(workflow) {
  return copy({ disclosure: DISCLOSURE, ...state(workflow) });
}
