package metadatair

import (
	"fmt"
	"strings"
)

type NodeKind string

const (
	NodeKindObject            NodeKind = "object"
	NodeKindField             NodeKind = "field"
	NodeKindRelationship      NodeKind = "relationship"
	NodeKindFlowDefinition    NodeKind = "flow_definition"
	NodeKindFlowVersion       NodeKind = "flow_version"
	NodeKindFlowElement       NodeKind = "flow_element"
	NodeKindFormulaExpression NodeKind = "formula_expression"
	NodeKindAccessPolicy      NodeKind = "access_policy"
	NodeKindConnectorContract NodeKind = "connector_contract"
)

func (k NodeKind) Valid() bool {
	switch k {
	case NodeKindObject,
		NodeKindField,
		NodeKindRelationship,
		NodeKindFlowDefinition,
		NodeKindFlowVersion,
		NodeKindFlowElement,
		NodeKindFormulaExpression,
		NodeKindAccessPolicy,
		NodeKindConnectorContract:
		return true
	default:
		return false
	}
}

type SourceFormat string

const (
	SourceFormatPackComponentJSON SourceFormat = "pack_component_json"
	SourceFormatFlowMetadataJSON  SourceFormat = "flow_metadata_json"
	SourceFormatFormulaString     SourceFormat = "formula_string"
	SourceFormatAccessPolicy      SourceFormat = "access_policy"
	SourceFormatConnectorContract SourceFormat = "connector_contract"
)

func (f SourceFormat) Valid() bool {
	switch f {
	case SourceFormatPackComponentJSON,
		SourceFormatFlowMetadataJSON,
		SourceFormatFormulaString,
		SourceFormatAccessPolicy,
		SourceFormatConnectorContract:
		return true
	default:
		return false
	}
}

type OwnerLayer string

const (
	OwnerLayerL0 OwnerLayer = "L0"
	OwnerLayerL1 OwnerLayer = "L1"
	OwnerLayerL2 OwnerLayer = "L2"
)

func (l OwnerLayer) Valid() bool {
	switch l {
	case OwnerLayerL0, OwnerLayerL1, OwnerLayerL2:
		return true
	default:
		return false
	}
}

// Owner records who authored the source fact. OwnerPack is empty for L0
// substrate facts and required for pack or overlay-authored facts.
type Owner struct {
	Layer     OwnerLayer
	OwnerPack string
}

func (o Owner) Validate() error {
	if !o.Layer.Valid() {
		return fmt.Errorf("metadata IR owner layer %q is not recognized", o.Layer)
	}
	if o.Layer == OwnerLayerL0 && strings.TrimSpace(o.OwnerPack) != "" {
		return fmt.Errorf("metadata IR L0 owner must not set owner pack %q", o.OwnerPack)
	}
	if o.Layer != OwnerLayerL0 && strings.TrimSpace(o.OwnerPack) == "" {
		return fmt.Errorf("metadata IR %s owner requires an owner pack", o.Layer)
	}
	return nil
}

// SourceLocation identifies the authoring surface and source path for one
// lowered node. Dialect fields are inert provenance only; engines must not
// branch on them.
type SourceLocation struct {
	Format         SourceFormat
	URI            string
	Component      string
	Path           string
	Line           int
	Column         int
	Dialect        string
	DialectVersion string
	Annotations    map[string]string
}

func (s SourceLocation) Validate() error {
	if !s.Format.Valid() {
		return fmt.Errorf("metadata IR source format %q is not recognized", s.Format)
	}
	if strings.TrimSpace(s.URI) == "" {
		return fmt.Errorf("metadata IR source URI is required")
	}
	if strings.TrimSpace(s.Component) == "" {
		return fmt.Errorf("metadata IR source component is required")
	}
	if strings.TrimSpace(s.Path) == "" {
		return fmt.Errorf("metadata IR source path is required")
	}
	if s.Line < 0 || s.Column < 0 {
		return fmt.Errorf("metadata IR source line and column must be non-negative")
	}
	if strings.TrimSpace(s.Dialect) == "" && strings.TrimSpace(s.DialectVersion) != "" {
		return fmt.Errorf("metadata IR source dialect version requires a dialect")
	}
	return nil
}

type TargetSurface string

const (
	TargetSurfaceNeutralIR TargetSurface = "neutral_ir"
)

func (s TargetSurface) Valid() bool {
	return s == TargetSurfaceNeutralIR
}

// LoweringBoundary records the explicit source-to-neutral-IR edge that
// produced the node. Runtime projections are intentionally not valid P1
// document nodes.
type LoweringBoundary struct {
	From SourceFormat
	To   TargetSurface
}

func (b LoweringBoundary) Validate() error {
	if !b.From.Valid() {
		return fmt.Errorf("metadata IR lowering source format %q is not recognized", b.From)
	}
	if !b.To.Valid() {
		return fmt.Errorf("metadata IR lowering target %q is not recognized", b.To)
	}
	return nil
}

type NodeID string

type NodeIdentity struct {
	Kind NodeKind
	ID   NodeID
}

func (i NodeIdentity) String() string {
	return string(i.Kind) + ":" + string(i.ID)
}

// NodeHeader is the shared, neutral header every lowered metadata node must
// carry before verifier or execution-planning work can consume it.
type NodeHeader struct {
	ID       NodeID
	Kind     NodeKind
	Owner    Owner
	Source   SourceLocation
	Boundary LoweringBoundary
}

func (n NodeHeader) Identity() NodeIdentity {
	return NodeIdentity{Kind: n.Kind, ID: n.ID}
}

func (n NodeHeader) Validate() error {
	if strings.TrimSpace(string(n.ID)) == "" {
		return fmt.Errorf("metadata IR node id is required")
	}
	if !n.Kind.Valid() {
		return fmt.Errorf("metadata IR node kind %q is not recognized", n.Kind)
	}
	identity := n.Identity()
	if err := n.Owner.Validate(); err != nil {
		return fmt.Errorf("metadata IR node %s owner invalid: %w", identity, err)
	}
	if err := n.Source.Validate(); err != nil {
		return fmt.Errorf("metadata IR node %s source invalid: %w", identity, err)
	}
	if err := n.Boundary.Validate(); err != nil {
		return fmt.Errorf("metadata IR node %s boundary invalid: %w", identity, err)
	}
	if n.Source.Format != n.Boundary.From {
		return fmt.Errorf("metadata IR node %s source format %q does not match lowering boundary %q", identity, n.Source.Format, n.Boundary.From)
	}
	return nil
}

type Document struct {
	Version Version
	Nodes   []NodeHeader
}

func (d Document) Validate() error {
	if err := d.Version.Validate(); err != nil {
		return err
	}
	_, err := d.Index()
	return err
}

// Index validates and indexes nodes by kind plus node id. Any duplicate
// identity fails closed because it would create competing read paths for the
// same fact.
func (d Document) Index() (map[NodeIdentity]NodeHeader, error) {
	out := make(map[NodeIdentity]NodeHeader, len(d.Nodes))
	for _, node := range d.Nodes {
		if err := node.Validate(); err != nil {
			return nil, err
		}
		identity := node.Identity()
		if _, exists := out[identity]; exists {
			return nil, fmt.Errorf("metadata IR duplicate node identity %s", identity)
		}
		out[identity] = node
	}
	return out, nil
}
