use crate::trait_data::{NamedData, SustainedData, TemporalData, RangedData, ArealData, FeaturedData, ObscurableData, OwnableData, AttachedData, TangibleData, ConnectorData, MovableData, ContainerData, QuantifiedData, ActivatableData, MorphableData, NarratedData, GatedData, SenseData, PerceivabilityFlags, TraitSet};
use crate::types::{EntityTypeId, SeatId};

/// Full initial state of an entity, carried in `GameEvent::EntityCreated`.
/// One interface for entity creation everywhere: setup code, handlers, and log replay
/// all go through `EntityCreated { id, blueprint }`.
///
/// # Field conventions
///
/// All traits: `Option<XxxData>` — `None` = trait not declared, `Some(data)` = declared.
/// `Tangible` carries the entity's board position (`TangibleData { position }`).
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq)]
pub struct EntityBlueprint {
    pub type_id:             EntityTypeId,
    pub named:               Option<NamedData>,
    pub sustained:           Option<SustainedData>,
    pub temporal:            Option<TemporalData>,
    pub ranged:              Option<RangedData>,
    pub areal:               Option<ArealData>,
    pub featured:            Option<FeaturedData>,
    /// Per-seat perceivability overrides (stored as ObservableData in entity store).
    /// Use `.perceivability(seat, flags)` to add entries.
    pub perceivability_overrides: Vec<(SeatId, PerceivabilityFlags)>,
    pub obscurable:          Option<ObscurableData>,
    pub ownable:             Option<OwnableData>,
    pub attached:            Option<AttachedData>,
    pub tangible:            Option<TangibleData>,
    pub connector:           Option<ConnectorData>,
    pub movable:             Option<MovableData>,
    pub container:           Option<ContainerData>,
    pub quantified:          Option<QuantifiedData>,
    pub activatable:         Option<ActivatableData>,
    pub morphable:           Option<MorphableData>,
    pub narrated:            Option<NarratedData>,
    pub gated:               Option<GatedData>,
    pub sense:               Option<SenseData>,
    /// S26/S27 marker bits — PRESENT, LOCATED, OBSERVABLE have no backing struct.
    pub present:             bool,
    pub located:             bool,
    /// Set the OBSERVABLE bit (entity data globally accessible to non-owners).
    /// For per-seat overrides, use `.perceivability(seat, flags)` instead or additionally.
    pub observable:          bool,
    /// Initial tag strings set on the spawned entity. PA-CG1-1 (2026-05-09):
    /// before this field, plugins with kind tags (`"chess:king"`,
    /// `"is_stunned"`) had to post-spawn-mutate `Entity.tags` because there
    /// was no blueprint surface. Drained into `Entity.tags` by the
    /// EntityCreated apply path. Build via `.tag("foo")` / `.tags(["a","b"])`.
    pub initial_tags:        crate::types::TagSet,
}

impl EntityBlueprint {
    pub fn new(type_id: EntityTypeId) -> Self {
        Self {
            type_id,
            named: None, sustained: None, temporal: None, ranged: None, areal: None, featured: None,
            perceivability_overrides: vec![],
            obscurable: None, ownable: None,
            attached: None, tangible: None, connector: None, movable: None, container: None,
            quantified: None, activatable: None, morphable: None,
            narrated: None,
            gated: None,
            sense: None,
            present: false,
            located: false,
            observable: false,
            initial_tags: crate::types::TagSet::new(),
        }
    }

    /// Add a single string tag that will be present on the spawned entity.
    /// Tags can also be added/removed at runtime via `GameEvent::TagAdded`
    /// / `TagRemoved`; this builder is for tags an entity has from creation
    /// (kind discriminators, immutable type markers).
    pub fn tag(mut self, t: impl AsRef<str>) -> Self {
        self.initial_tags.add(t.as_ref());
        self
    }

    /// Add multiple initial tags. Convenience over chained `.tag(...)` calls.
    pub fn tags<I: IntoIterator<Item = S>, S: AsRef<str>>(mut self, it: I) -> Self {
        for t in it { self.initial_tags.add(t.as_ref()); }
        self
    }

    pub fn trait_set(&self) -> TraitSet {
        let mut ts = TraitSet::empty();
        if self.named.is_some()               { ts |= TraitSet::NAMED; }
        if self.sustained.is_some()           { ts |= TraitSet::SUSTAINED; }
        if self.temporal.is_some()            { ts |= TraitSet::TEMPORAL; }
        if self.ranged.is_some()              { ts |= TraitSet::RANGED; }
        if self.areal.is_some()               { ts |= TraitSet::AREAL; }
        if self.featured.is_some()            { ts |= TraitSet::FEATURED; }
        if self.tangible.is_some()            { ts |= TraitSet::TANGIBLE; }
        if self.connector.is_some()           { ts |= TraitSet::CONNECTOR; }
        if self.observable || !self.perceivability_overrides.is_empty() { ts |= TraitSet::OBSERVABLE; }
        if self.obscurable.is_some()          { ts |= TraitSet::OBSCURABLE; }
        if self.ownable.is_some()             { ts |= TraitSet::OWNABLE; }
        if self.attached.is_some()            { ts |= TraitSet::ATTACHED; }
        if self.movable.is_some()             { ts |= TraitSet::MOVABLE; }
        if self.container.is_some()           { ts |= TraitSet::CONTAINER; }
        if self.quantified.is_some()          { ts |= TraitSet::QUANTIFIED; }
        if self.activatable.is_some()         { ts |= TraitSet::ACTIVATABLE; }
        if self.morphable.is_some()           { ts |= TraitSet::MORPHABLE; }
        if self.gated.is_some()               { ts |= TraitSet::GATED; }
        if self.sense.is_some()               { ts |= TraitSet::SENSE; }
        if self.present                       { ts |= TraitSet::PRESENT; }
        if self.located                       { ts |= TraitSet::LOCATED; }
        // observable marker already handled above alongside perceivability_overrides
        ts
    }

    /// Add display data (name/icon/description). `Featured` requires `Named`,
    /// so call this on every blueprint that also sets `.featured(...)`.
    pub fn named(mut self, d: NamedData) -> Self                  { self.named = Some(d); self }
    /// Mark this entity as a sustained effect bound to its owner. The host
    /// enforces `(group, owner)` exclusivity. See [`SustainedData`].
    pub fn sustained(mut self, d: SustainedData) -> Self          { self.sustained = Some(d); self }
    /// Give this entity a finite duration. The host automatically decrements
    /// `remaining` at the matching `TickTrigger` and synthesizes
    /// `EntityDestroyed` when it hits 0. See [`TemporalData`].
    pub fn temporal(mut self, d: TemporalData) -> Self            { self.temporal = Some(d); self }
    /// Declare a range constraint that the host enforces on `Activated`
    /// events. Pairs with `Activatable`. See [`RangedData`].
    pub fn ranged(mut self, d: RangedData) -> Self                { self.ranged = Some(d); self }
    /// Declare this entity's area-of-effect shape. Pairs with
    /// `Activatable` (and often `Ranged`) for AoE abilities. See
    /// [`ArealData`] / [`AreaShape`].
    pub fn areal(mut self, d: ArealData) -> Self                  { self.areal = Some(d); self }
    pub fn featured(mut self, d: FeaturedData) -> Self            { self.featured = Some(d); self }
    /// Place this entity on the board at the given position. Tangible carries position;
    /// use `TangibleData::at(Position::Face(FaceId((((row) * 8 + (col)) as u32))))`.
    pub fn tangible(mut self, d: TangibleData) -> Self            { self.tangible = Some(d); self }
    /// Declare this entity as a connector that links a set of other entities
    /// (roads, bridges, graph edges). See `ConnectorData`.
    pub fn connector(mut self, d: ConnectorData) -> Self          { self.connector = Some(d); self }
    /// Set the OBSERVABLE marker bit — entity data is globally accessible to non-owners.
    /// For per-seat overrides use `.perceivability(seat, flags)` additionally or instead.
    pub fn observable(mut self) -> Self                           { self.observable = true; self }
    /// Add a per-seat perceivability override (stored as `ObservableData` in entity store).
    pub fn perceivability(mut self, seat: SeatId, flags: PerceivabilityFlags) -> Self {
        self.perceivability_overrides.push((seat, flags));
        self
    }
    pub fn obscurable(mut self, d: ObscurableData) -> Self        { self.obscurable = Some(d); self }
    pub fn ownable(mut self, d: OwnableData) -> Self              { self.ownable = Some(d); self }
    pub fn attached(mut self, d: AttachedData) -> Self            { self.attached = Some(d); self }
    pub fn movable(mut self, d: MovableData) -> Self              { self.movable = Some(d); self }
    pub fn container(mut self, d: ContainerData) -> Self          { self.container = Some(d); self }
    pub fn quantified(mut self, d: QuantifiedData) -> Self        { self.quantified = Some(d); self }
    pub fn activatable(mut self, d: ActivatableData) -> Self      { self.activatable = Some(d); self }
    pub fn morphable(mut self, d: MorphableData) -> Self          { self.morphable = Some(d); self }
    pub fn narrated(mut self, d: NarratedData) -> Self            { self.narrated = Some(d); self }
    /// Declare a predicate gating when this entity is offered / selectable /
    /// activatable. The host evaluates `data.when` against the current store
    /// and exposes `Predicate::explain` for client UIs. See [`GatedData`].
    pub fn gated(mut self, d: GatedData) -> Self                  { self.gated = Some(d); self }
    /// Declare this entity as a SENSE source. Always pair with `.attached(...)`.
    /// The SENSE entity grants perceivability overrides to entities in the observer's range.
    pub fn sense(mut self, d: SenseData) -> Self                  { self.sense = Some(d); self }
    /// Mark this entity as detectable by non-owners (PRESENT marker bit).
    pub fn present(mut self) -> Self                              { self.present = true; self }
    /// Mark this entity's position as knowable to non-owners (LOCATED marker bit).
    pub fn located(mut self) -> Self                              { self.located = true; self }

    /// Materialize an `EntityView` for the given id from this blueprint.
    ///
    /// Used by client-side `EntityCreated` handlers to render newly-spawned
    /// entities without waiting for the next per-recipient Snapshot. The
    /// resulting view carries every trait-data field from the blueprint,
    /// matching the shape `entity_view_for` produces server-side for the
    /// owner's own view.
    ///
    /// **Note on perceivability:** the client trusts the wire — if the
    /// server forwarded an `EntityCreated` to this player, the player is
    /// allowed to see the blueprint. Per-seat perceivability filtering
    /// happens server-side before the event reaches the wire. The
    /// blueprint's marker bits and `perceivability_overrides` are preserved
    /// in the entity store but don't shape the receiver's view here.
    pub fn to_entity_view(&self, id: crate::types::EntityId) -> crate::entity_state::EntityView {
        use std::sync::Arc;
        crate::entity_state::EntityView {
            id,
            type_id: Some(self.type_id),
            named:       self.named.clone().map(Arc::new),
            sustained:   self.sustained,
            temporal:    self.temporal,
            ranged:      self.ranged.clone(),
            areal:       self.areal,
            featured:    self.featured.clone(),
            tangible:    self.tangible,
            connector:   self.connector.clone().map(Arc::new),
            observable:  None,
            ownable:     self.ownable.clone(),
            attached:    self.attached,
            movable:     self.movable.clone(),
            quantified:  self.quantified.clone().map(Arc::new),
            activatable: self.activatable.clone().map(Arc::new),
            obscurable:  self.obscurable.clone(),
            morphable:   self.morphable.clone().map(Arc::new),
            container:   self.container.clone().map(Arc::new),
            gated:       self.gated.clone(),
            ..Default::default()
        }
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::predicate::Predicate;
    use crate::types::EntityTypeId;

    #[test]
    fn blueprint_gated_default_is_none() {
        let bp = EntityBlueprint::new(EntityTypeId::from_raw(1));
        assert!(bp.gated.is_none());
        assert!(!bp.trait_set().contains(TraitSet::GATED));
    }

    #[test]
    fn blueprint_with_gated_sets_bit_and_stores_data() {
        let bp = EntityBlueprint::new(EntityTypeId::from_raw(1))
            .gated(GatedData::when(Predicate::All(vec![])));
        assert_eq!(
            bp.gated.as_ref().map(|g| g.when.clone()),
            Some(Predicate::All(vec![]))
        );
        assert!(bp.trait_set().contains(TraitSet::GATED));
    }
}
