using ERPCore.Domain.Enums;

namespace ERPCore.Domain.Entities;

/// <summary>
/// Item master (FR-MD-01). Mutable aggregate: carries a <see cref="RowVersion"/>
/// concurrency token surfaced as an ETag (docs/10 Part C.10). SKU is unique.
/// Model: docs/10-BACKEND-PHASE1.md Part C.1.
/// </summary>
public class Item
{
    public int ItemId { get; set; }
    public string Sku { get; set; } = string.Empty;
    public string Name { get; set; } = string.Empty;
    public string? Description { get; set; }

    public int CategoryId { get; set; }
    public Category? Category { get; set; }

    /// <summary>Optional second level below <see cref="CategoryId"/>; must belong to it.</summary>
    public int? SubCategoryId { get; set; }
    public SubCategory? SubCategory { get; set; }

    public int? BrandId { get; set; }
    public Brand? Brand { get; set; }

    /// <summary>
    /// The stocking unit — the pack the item is counted in (BOTTLE, PACKET, BOX, PCS).
    /// <b>Every</b> quantity in the system is a count of these: stock layers, the ledger,
    /// and every document line. Nothing converts, so this is the sole meaning of a
    /// quantity and cannot be changed once the item has stock history.
    /// </summary>
    public int BaseUomId { get; set; }
    public Uom? BaseUom { get; set; }

    public int? DefaultVendorId { get; set; }
    public Vendor? DefaultVendor { get; set; }

    public StockNature StockNature { get; set; }
    public TrackingMode TrackingMode { get; set; }
    public Warranty Warranty { get; set; } = Warranty.NonWarranty;
    /// <summary>Coverage length in months (see <see cref="Enums.WarrantyPeriods.AllowedMonths"/>) when <see cref="Warranty"/> is Warranty; null otherwise.</summary>
    public int? WarrantyPeriodMonths { get; set; }
    public string? TaxClass { get; set; }

    /// <summary>
    /// Optional fixed selling price used by Sales only. <c>null</c> means "use stock value"
    /// (the item is sold at its FIFO stock cost at sale time); a value is the fixed sale price.
    /// Never enters costing/GRN/FIFO (docs/10 Part C.1, C.9).
    /// </summary>
    public decimal? SalePrice { get; set; }

    /// <summary>
    /// How much one pack holds, as the user entered it — <c>500</c> with
    /// <see cref="ContentUnit"/> <c>Ml</c> for a 500 ml bottle, <c>1.5</c> with <c>L</c>
    /// for a 1.5 L one. Null (together with the other three) when the item has no
    /// measurable content: a screw, a label, a service.
    /// <para>
    /// Content never affects stock — that is always a pack count. It exists so production
    /// can express a formula in millilitres or grams and resolve it to packs
    /// (see <c>IItemMeasure</c>).
    /// </para>
    /// <para>
    /// A loose bulk item bought by weight is modelled the same way:
    /// <c>BaseUom = KG, ContentQty = 1, ContentUnit = Kg</c> ⇒ 1000 g per stocked unit.
    /// </para>
    /// </summary>
    public decimal? ContentQty { get; set; }
    public MeasureUnit? ContentUnit { get; set; }

    /// <summary>
    /// <see cref="ContentQty"/>/<see cref="ContentUnit"/> normalised to a base unit
    /// (L→Ml, Kg→G, both ×1000) at write time by <c>ItemContent.Normalize</c>. Server-derived
    /// and never accepted from a client. <see cref="ContentBaseUnit"/> is therefore only ever
    /// <see cref="MeasureUnit.Ml"/> or <see cref="MeasureUnit.G"/>.
    /// <para>Stored rather than recomputed so every consumer reads one settled number.</para>
    /// </summary>
    public decimal? ContentBaseQty { get; set; }
    public MeasureUnit? ContentBaseUnit { get; set; }

    public EntityStatus Status { get; set; } = EntityStatus.Active;

    public DateTime CreatedAt { get; set; }
    public DateTime? UpdatedAt { get; set; }

    /// <summary>PostgreSQL xmin-backed optimistic concurrency token (ETag source).</summary>
    public uint RowVersion { get; set; }

    public ICollection<ItemReorder> ReorderSettings { get; set; } = new List<ItemReorder>();
}
