// Manufacturing / Production Lines DTOs (docs/30-BACKEND-PHASE2.md §D.1–D.3; FR-MFG-01..20).
// Mirrors the backend DTOs exactly — the API contract is the source of truth.
//
// Three things changed when the real backend landed, and they are worth knowing if you are
// reading old code or docs/21-FRONTEND-PHASE2.md:
//   * a template is identified by `code`, not `docNo` (only runs carry a document number)
//   * quantities reference an `itemId` numeric FK with `qtyPerBatch` — not free text. Lines
//     carry no UOM: an input's unit is `qtyUnit` (packs or the item's content unit) and an
//     output's is its item's base UOM, or `uomId` when it is intermediate WIP
//   * stages carry `posX`/`posY`, so canvas layout round-trips through the server
//
// The `key` vocabulary (§D.1): every stage and output has a client-facing string key
// alongside its database id. On a GET the key IS the stringified id; on a save you echo
// those keys back for rows you kept and mint `tmp-<uuid>` keys for rows you just drew.
// Edges and Upstream inputs then reference stages/outputs *by key only*, which is what lets
// one payload shape serve both create (nothing has an id) and update (most things do).

export type TemplateStatus = "Active" | "Inactive"
export type ProductionRunStatus = "InProgress" | "Completed" | "Cancelled"
export type RunStageStatus = "Waiting" | "Ready" | "InProgress" | "Done" | "Approved"
export type StageInputSource = "Stock" | "Upstream"

/**
 * What a stage input's quantity is expressed in.
 *
 * `Pack` is a count of the item's base UOM. `Content` is an amount of the item's content in
 * its base content unit (ml or g), which the server divides by the item's content size to get
 * packs — 2000 ml of a 500 ml bottle consumes 4, and 300 ml consumes 0.6. `Content` requires
 * the item to have a content size, and Upstream inputs must always be `Pack`.
 */
export type StageQtyUnit = "Pack" | "Content"
export type CustomFieldType = "Text" | "Number" | "Checkbox" | "Date" | "Select"

export type RunStageEventType =
  | "Start"
  | "Complete"
  | "Approve"
  | "Transfer"
  | "RejectIntake"
  | "TerminalReject"
  | "LeftoverReturn"
  | "Cancel"
  | "QuantityEdit"

// --- templates ---------------------------------------------------------------

/** One custom field definition, stored verbatim in the stage's `fieldDefs` jsonb (FR-MFG-07). */
export interface FieldDef {
  key: string
  label: string
  type: CustomFieldType
  /** Only read when `type` is `Select`. */
  options: string[] | null
  required: boolean
}

export interface StageInput {
  inputId: number
  source: StageInputSource
  /** Set when `source` is `Stock`; null when `Upstream`. */
  itemId: number | null
  /** Set when `source` is `Upstream` — an output of a *direct* parent stage. */
  fromOutputId: number | null
  fromOutputKey: string | null
  qtyUnit: StageQtyUnit
  qtyPerBatch: number
}

export interface StageOutput {
  outputId: number
  key: string
  /** Null on intermediate (WIP) outputs; required on the terminal stage's single output. */
  itemId: number | null
  name: string
  /** WIP display label — set exactly when `itemId` is null; the unit otherwise comes from the item. */
  uomId: number | null
  qtyPerBatch: number
}

export interface TemplateStage {
  stageId: number
  key: string
  name: string
  roleLabel: string | null
  estimatedMinutes: number
  posX: number
  posY: number
  fieldDefs: FieldDef[]
  inputs: StageInput[]
  outputs: StageOutput[]
}

export interface TemplateEdge {
  edgeId: number
  parentStageId: number
  childStageId: number
  parentKey: string
  childKey: string
}

/** Row on the template list. `activeRunCount > 0` means the builder is edit-locked (FR-MFG-06). */
export interface ProductionTemplateSummary {
  templateId: number
  code: string
  name: string
  status: TemplateStatus
  stageCount: number
  /** Ordered by stage id — enough to label the overview canvas without fetching each graph. */
  stageNames: string[]
  activeRunCount: number
  createdBy: number
  createdAt: string
}

/**
 * A canvas grouping box or divider line. Round-tripped verbatim through a jsonb column and
 * completely invisible to the graph validator — annotations have no ports, no edges, and no
 * bearing on whether a template is valid.
 */
export interface CanvasAnnotation {
  kind: "box" | "line"
  posX: number
  posY: number
  width: number
  height: number
  label: string | null
  /** Degrees. Lines only. */
  rotation: number | null
}

export interface ProductionTemplateGraph {
  templateId: number
  code: string
  name: string
  description: string | null
  status: TemplateStatus
  stages: TemplateStage[]
  edges: TemplateEdge[]
  annotations: CanvasAnnotation[]
  /** `> 0` puts the builder in its read-only edit-locked state (FR-MFG-06). */
  activeRunCount: number
  createdBy: number
  createdAt: string
}

// Save payloads. Narrow by design — status, ids, createdBy and timestamps are all
// server-controlled and rejected if sent (02-SECURITY §B.6).

export interface SaveStageInputInput {
  source: StageInputSource
  itemId?: number | null
  fromOutputKey?: string | null
  qtyUnit: StageQtyUnit
  qtyPerBatch: number
}

export interface SaveStageOutputInput {
  key: string
  itemId?: number | null
  name: string
  /** Required when `itemId` is null (WIP); must be null when it is set. */
  uomId?: number | null
  qtyPerBatch: number
}

export interface SaveStageInput {
  key: string
  name: string
  roleLabel?: string | null
  estimatedMinutes: number
  posX: number
  posY: number
  fieldDefs: FieldDef[]
  inputs: SaveStageInputInput[]
  outputs: SaveStageOutputInput[]
}

export interface SaveEdgeInput {
  parentKey: string
  childKey: string
}

export interface SaveTemplateRequest {
  code: string
  name: string
  description?: string | null
  stages: SaveStageInput[]
  /** Empty is legal — a single-stage template is both entry and terminal. */
  edges: SaveEdgeInput[]
  /** Replaced wholesale on every save. Server caps the array at 200. */
  annotations: CanvasAnnotation[]
}

// --- runs --------------------------------------------------------------------

/** Per-status stage counts driving the board's progress strip (FR-MFG-18). */
export interface StageSummary {
  waiting: number
  ready: number
  inProgress: number
  done: number
  approved: number
}

/** Derived server-side, never stored: `consumed − returned` across every run input. */
export interface CostPool {
  consumed: number
  returned: number
  net: number
}

export interface RunStageInput {
  runInputId: number
  source: StageInputSource
  itemId: number | null
  fromRunOutputId: number | null
  qtyUnit: StageQtyUnit
  /** Expressed in `qtyUnit`. Editable until the stage starts. */
  plannedQty: number
  /**
   * The four figures below are always a count of the item's *base* UOM — the only unit the
   * FIFO engine and the ledger speak. For a `Content` input they are therefore in different
   * units from plannedQty: 2000 ml planned against a 500 ml bottle shows consumedQty 4. Do
   * not compare them to plannedQty without dividing by the item's content size.
   */
  consumedQty: number
  consumedValue: number
  returnedQty: number
  returnedValue: number
  /** Upstream inputs only: accumulated by parent transfers. */
  deliveredQty: number
}

export interface RunStageOutput {
  runOutputId: number
  itemId: number | null
  name: string
  /** WIP display label; null on the terminal output, whose unit is the finished item's base UOM. */
  uomId: number | null
  /** Always a pack count — scrap is recorded in whole units, never in ml or g. */
  plannedQty: number
  producedQty: number
  scrappedQty: number
  scrapReasonCodeId: number | null
  transferredQty: number
  /** Derived: produced − scrapped − transferred (FR-MFG-12). */
  availableToTransfer: number
}

export interface RunStage {
  runStageId: number
  templateStageId: number | null
  name: string
  roleLabel: string | null
  estimatedMinutes: number
  posX: number
  posY: number
  status: RunStageStatus
  /** Derived from the run edge set — no outbound edge. */
  isTerminal: boolean
  /** Derived — no inbound edge; Ready from run creation. */
  isEntry: boolean
  actualStartAt: string | null
  actualEndAt: string | null
  /** Whole minutes once finished; null while still running (FR-MFG-19). */
  actualMinutes: number | null
  fieldDefs: FieldDef[]
  /** Captured at complete, keyed by `fieldDefs[].key`. */
  fieldValues: Record<string, unknown> | null
  inputs: RunStageInput[]
  outputs: RunStageOutput[]
}

export interface RunEdge {
  runEdgeId: number
  parentRunStageId: number
  childRunStageId: number
}

export interface RunEvent {
  eventId: number
  /** Null for run-level events (cancel). */
  runStageId: number | null
  eventType: RunStageEventType
  note: string | null
  payload: Record<string, unknown> | null
  userId: number
  createdAt: string
}

/** Row on the run board (docs/30 §D.2). */
export interface ProductionRunSummary {
  runId: number
  docNo: string
  templateId: number
  templateName: string
  targetQty: number
  status: ProductionRunStatus
  reworkCount: number
  stageSummary: StageSummary
  warehouseId: number
  finishedItemId: number | null
  finishedItemName: string | null
  createdBy: number
  createdAt: string
  completedAt: string | null
}

export interface ProductionRunGraph {
  runId: number
  docNo: string
  templateId: number
  templateName: string
  warehouseId: number
  outputBinId: number | null
  targetQty: number
  scaleFactor: number
  status: ProductionRunStatus
  reworkCount: number
  cancelReasonCodeId: number | null
  costPool: CostPool
  stages: RunStage[]
  edges: RunEdge[]
  events: RunEvent[]
  createdBy: number
  createdAt: string
  completedAt: string | null
}

export interface CreateRunRequest {
  templateId: number
  targetQty: number
  warehouseId: number
  outputBinId?: number | null
}

/** `id` is the runInputId or runOutputId being adjusted. */
export interface StageQuantityLine {
  id: number
  plannedQty: number
}

export interface UpdateStageQuantitiesRequest {
  inputs: StageQuantityLine[]
  outputs: StageQuantityLine[]
}

// --- stage action results ----------------------------------------------------

export interface ConsumedLayer {
  layerId: number
  qty: number
  unitCost: number
}

export interface ConsumedInput {
  runInputId: number
  itemId: number
  /** Base UOM. */
  qty: number
  value: number
  consumedLayers: ConsumedLayer[]
}

export interface StartStageResult {
  runStageId: number
  status: RunStageStatus
  actualStartAt: string | null
  consumed: ConsumedInput[]
  ledgerRefs: number[]
  stage: RunStage
}

export interface CompleteOutputLine {
  runOutputId: number
  producedQty: number
  scrappedQty: number
  /** Required once `scrappedQty > 0`; must be a Production-context reason. */
  scrapReasonCodeId?: number | null
}

export interface CompleteStageRequest {
  outputs: CompleteOutputLine[]
  fieldValues?: Record<string, unknown> | null
}

export interface Transfer {
  runOutputId: number
  runInputId: number
  childRunStageId: number
  qty: number
  childDeliveredQty: number
  childStatus: RunStageStatus
}

export interface Receipt {
  layerId: number
  itemId: number
  warehouseId: number
  binId: number | null
  qtyReceived: number
  unitCost: number
  value: number
}

export interface ApproveStageResult {
  runStageId: number
  status: RunStageStatus
  runStatus: ProductionRunStatus
  transfers: Transfer[]
  /** Terminal approve only. */
  receipt: Receipt | null
  costPool: CostPool | null
  ledgerRefs: number[]
  stage: RunStage
}

export interface TransferLine {
  runOutputId: number
  qty: number
  /** Optional explicit target when one output feeds several child inputs. */
  runInputId?: number | null
}

export interface ApproveStageRequest {
  /** Omit or leave empty to transfer the full available quantity of every output. */
  transfers?: TransferLine[]
}

export interface TransferRemainderRequest {
  runOutputId: number
  qty: number
  runInputId?: number | null
}

export interface TransferResult {
  runStageId: number
  transfers: Transfer[]
  stage: RunStage
}

export interface ReturnLeftoverRequest {
  /** In the item's *base* UOM, matching `consumedQty`. */
  qty: number
  reasonCodeId: number
}

export interface ReturnLeftoverResult {
  runInputId: number
  returnedQty: number
  returnedValue: number
  createdLayer: { layerId: number; unitCost: number }
  ledgerRefs: number[]
  costPool: CostPool
}

export interface RejectRequest {
  note?: string | null
}

export interface PulledBack {
  runInputId: number
  parentRunStageId: number
  parentRunOutputId: number
  qty: number
  priorParentStatus: RunStageStatus
  parentStatus: RunStageStatus
}

export interface RejectIntakeResult {
  runStageId: number
  status: RunStageStatus
  pulledBack: PulledBack[]
  run: ProductionRunGraph
}

export interface TerminalRejectResult {
  runStageId: number
  reworkCount: number
  run: ProductionRunGraph
}

export interface CancelRunRequest {
  reasonCodeId: number
  note?: string | null
}

export interface CancelReturn {
  itemId: number
  qty: number
  unitCost: number
  layerId: number
}

/** Scrap never entered stock, so it is written off on the event rather than returned. */
export interface ScrapWriteOff {
  runOutputId: number
  name: string
  qty: number
}

export interface CancelRunResult {
  runId: number
  status: ProductionRunStatus
  returns: CancelReturn[]
  scrappedWrittenOff: ScrapWriteOff[]
  ledgerRefs: number[]
}
