using System.ComponentModel.DataAnnotations;
using System.Text.Json;
using ERPCore.Domain.Enums;

namespace ERPCore.Dtos.Production;

// Stage-action contract (docs/30-BACKEND-PHASE2.md §D.3). Every action is transactional,
// guards on the stage's current status, and returns the refreshed stage plus whatever
// stock effects it caused.

// --- start -------------------------------------------------------------------

/// <summary>One FIFO layer a stage start drew from, at that layer's cost.</summary>
public sealed record ConsumedLayerDto(int LayerId, decimal Qty, decimal UnitCost);

/// <summary>What a single Stock input consumed at start (FR-MFG-10).</summary>
public sealed record ConsumedInputDto(
    int RunInputId, int ItemId, decimal Qty, decimal Value, IReadOnlyList<ConsumedLayerDto> ConsumedLayers);

public sealed record StartStageResultDto(
    int RunStageId, ProductionStageStatus Status, DateTime? ActualStartAt,
    IReadOnlyList<ConsumedInputDto> Consumed, IReadOnlyList<int> LedgerRefs,
    RunStageDto Stage);

// --- complete ----------------------------------------------------------------

public sealed class CompleteStageRequest
{
    [Required, MinLength(1)]
    public List<CompleteOutputLine> Outputs { get; set; } = new();

    /// <summary>
    /// Values for the stage's custom fields, keyed by <c>fieldDefs[].key</c>. Every field
    /// marked required must be present and non-empty (<c>400 REQUIRED_FIELD_MISSING</c>).
    /// </summary>
    public JsonElement? FieldValues { get; set; }
}

public sealed class CompleteOutputLine
{
    [Range(1, int.MaxValue)]
    public int RunOutputId { get; set; }

    [Range(0, double.MaxValue)]
    public decimal ProducedQty { get; set; }

    [Range(0, double.MaxValue)]
    public decimal ScrappedQty { get; set; }

    /// <summary>Mandatory once <see cref="ScrappedQty"/> &gt; 0; must be a Production reason.</summary>
    public int? ScrapReasonCodeId { get; set; }
}

// --- approve / transfer ------------------------------------------------------

/// <summary>
/// One WIP hand-off from a parent output to a child input. Deliveries route by
/// <c>fromRunOutputId</c>, not by edge — see the note on <c>RunEdge</c>.
/// </summary>
public sealed record TransferDto(
    int RunOutputId, int RunInputId, int ChildRunStageId, decimal Qty,
    decimal ChildDeliveredQty, ProductionStageStatus ChildStatus);

/// <summary>The finished-goods layer a terminal approve created (FR-MFG-13).</summary>
public sealed record ReceiptDto(
    int LayerId, int ItemId, int WarehouseId, int? BinId,
    decimal QtyReceived, decimal UnitCost, decimal Value);

public sealed record ApproveStageResultDto(
    int RunStageId, ProductionStageStatus Status, ProductionRunStatus RunStatus,
    IReadOnlyList<TransferDto> Transfers,
    ReceiptDto? Receipt, CostPoolDto? CostPool, IReadOnlyList<int> LedgerRefs,
    RunStageDto Stage);

public sealed class ApproveStageRequest
{
    /// <summary>
    /// Optional partial transfers. Omitted or empty means transfer the full available
    /// quantity of every output (FR-MFG-12).
    /// </summary>
    public List<TransferLine> Transfers { get; set; } = new();
}

public sealed class TransferLine
{
    [Range(1, int.MaxValue)]
    public int RunOutputId { get; set; }

    [Range(0.0001, double.MaxValue)]
    public decimal Qty { get; set; }

    /// <summary>
    /// Optional explicit target. When an output feeds several child inputs the server
    /// otherwise fills them in <c>runInputId</c> order; naming one removes the ambiguity.
    /// </summary>
    public int? RunInputId { get; set; }
}

public sealed class TransferRemainderRequest
{
    [Range(1, int.MaxValue)]
    public int RunOutputId { get; set; }

    [Range(0.0001, double.MaxValue)]
    public decimal Qty { get; set; }

    public int? RunInputId { get; set; }
}

public sealed record TransferResultDto(
    int RunStageId, IReadOnlyList<TransferDto> Transfers, RunStageDto Stage);

// --- leftover return ---------------------------------------------------------

public sealed class ReturnLeftoverRequest
{
    /// <summary>
    /// Quantity to return, in the item's <b>base</b> UOM — the same unit
    /// <c>consumedQty</c>/<c>returnedQty</c> are stored in, since the return posts straight
    /// to stock. Cannot exceed consumed − already returned.
    /// </summary>
    [Range(0.0001, double.MaxValue)]
    public decimal Qty { get; set; }

    /// <summary>Mandatory, and must be a Production-context reason (FR-MFG-14).</summary>
    public int? ReasonCodeId { get; set; }
}

public sealed record ProductionCreatedLayerDto(int LayerId, decimal UnitCost);

public sealed record ReturnLeftoverResultDto(
    int RunInputId, decimal ReturnedQty, decimal ReturnedValue,
    ProductionCreatedLayerDto CreatedLayer, IReadOnlyList<int> LedgerRefs, CostPoolDto CostPool);

// --- rejection / rework ------------------------------------------------------

public sealed class RejectRequest
{
    [StringLength(500)]
    public string? Note { get; set; }
}

/// <summary>One parent whose delivered work was pulled back by a reject-intake (FR-MFG-15).</summary>
public sealed record PulledBackDto(
    int RunInputId, int ParentRunStageId, int ParentRunOutputId,
    decimal Qty, ProductionStageStatus PriorParentStatus, ProductionStageStatus ParentStatus);

public sealed record RejectIntakeResultDto(
    int RunStageId, ProductionStageStatus Status,
    IReadOnlyList<PulledBackDto> PulledBack, RunGraphDto Run);

public sealed record TerminalRejectResultDto(
    int RunStageId, int ReworkCount, RunGraphDto Run);

// --- cancel ------------------------------------------------------------------

public sealed class CancelRunRequest
{
    public int? ReasonCodeId { get; set; }

    [StringLength(500)]
    public string? Note { get; set; }
}

public sealed record CancelReturnDto(int ItemId, decimal Qty, decimal UnitCost, int LayerId);

/// <summary>
/// Scrapped output quantities written off by a cancel. Recorded on the event only — scrap
/// sits on outputs, which never entered stock, so there is nothing to return (FR-MFG-17).
/// </summary>
public sealed record ScrapWriteOffDto(int RunOutputId, string Name, decimal Qty);

public sealed record CancelRunResultDto(
    int RunId, ProductionRunStatus Status,
    IReadOnlyList<CancelReturnDto> Returns,
    IReadOnlyList<ScrapWriteOffDto> ScrappedWrittenOff,
    IReadOnlyList<int> LedgerRefs);
