@Ecore(nsURI="https://release.models.nasdanika.org", nsPrefix="org.nasdanika.models.release")
@GenModel(
	modelDirectory="/model/src-gen",
    featureDelegation="Dynamic",
    complianceLevel="25",
    suppressGenModelAnnotations="false",
    copyrightFields="false",
    operationReflection="true",
    importOrganizing="true"
)
package org.nasdanika.models.release


import org.nasdanika.models.nxcore.Evaluator
import org.nasdanika.models.nxcore.ModelElement
import org.nasdanika.models.nxcore.NamedElement
import org.nasdanika.models.release.ReleasePackage

annotation "http://www.eclipse.org/emf/2002/Ecore" as Ecore
annotation "http://www.eclipse.org/emf/2002/GenModel" as GenModel
annotation "urn:org.nasdanika" as Nasdanika

/* ===========================================================================
 * SHARED ENUMERATIONS
 * =========================================================================== */

/*
 * How a product or release numbers itself. Semantic versioning per semver.org,
 * calendar versioning per calver.org, or something else entirely, which is
 * honest for products that predate the decision.
 */
enum VersioningScheme {
    SEMANTIC
    CALENDAR
    OTHER
}

/*
 * The size of a change, in semantic versioning terms. Used both for the bump
 * a version actually made and for the bump a compatibility analyzer says was
 * required. The two differing is the finding worth surfacing.
 */
enum ChangeLevel {
    NONE
    PATCH
    MINOR
    MAJOR
}

/*
 * Where a version or a release sits between plan and history. Deliberately
 * short, and deliberately local: when the lifecycle model is wired in, this
 * gives way to Staged and a lifecycle definition, which is how state is
 * handled everywhere else in the tower.
 */
enum ReleaseStatus {
    PLANNED
    IN_PROGRESS
    RELEASED
    /* Pulled after publication: an npm unpublish, a yanked crate, a retracted tag. */
    WITHDRAWN
}

enum Visibility {
    PUBLIC
    PRIVATE
    /* Visible inside the owning organization: GitHub internal, GitLab internal. */
    INTERNAL
}

/* ===========================================================================
 * PRODUCTS
 * =========================================================================== */

/*
 * Common base for defined products, product references and product catalogs,
 * so containment and federation treat all three uniformly.
 */
interface AbstractProduct extends ModelElement {
}

/*
 * A catalog of products, and the resource root.
 *
 * It mixes in the release, repository and registry catalogs so that one
 * resource can carry everything a release needs, and so that a federated
 * resource contributes its own repositories and registries along with its
 * products instead of depending on a central file to declare them.
 *
 * Catalogs nest, and the nesting is load bearing: it is the structure that
 * generated aggregator and category poms follow. A catalog named "tower"
 * inside a catalog named "models" produces a tower pom that the models pom
 * includes as a module.
 */
class ProductCatalog extends NamedElement, AbstractProduct, ReleaseCatalog, RepositoryCatalog, RegistryCatalog {

    @Nasdanika(logicalContainment="false")
    contains AbstractProduct[] products keys ^id

    @Nasdanika(logicalContainment="true")
    refers derived transient volatile readonly AbstractProduct[] resolvedProducts keys ^id get {
        val result = new org.eclipse.emf.common.util.BasicEList<AbstractProduct>()
        for (product : products) {
            val resolved = resolveProductReference(product)
            if (resolved !== null) result.add(resolved)
        }
        result
    }

    op AbstractProduct resolveProductReference(AbstractProduct start) {
        var AbstractProduct current = start
        val seen = new java.util.HashSet<AbstractProduct>()
        while (current instanceof ProductReference) {
            if (!seen.add(current)) {
                return null // cycle
            }
            current = current.target
            if (current === null) return null // dangling
        }
        return current
    }

    /* Product kinds contributed by this catalog; kinds resolve across a federation by URI. */
    contains ProductKind[] productKinds

    /*
     * Pom generation directives for this catalog. Present only on catalogs
     * meant to produce a pom, so containment is the marking that selects a
     * catalog for generation.
     */
    contains PomGeneration[] poms
}

/*
 * A kind of product: library, application, extension, archetype, template,
 * demo, model. A taxonomy rather than a flat list, so "every model" and
 * "every publishable artifact" are both answerable.
 */
class ProductKind extends ModelElement {
    refers ProductKind superType
}

/*
 * Something that is released and has versions.
 *
 * The unit of independent versioning, in the sense Lerna gave the word: each
 * product moves at its own pace, and the artifact that says which versions
 * work together is the generated bill of materials rather than a shared
 * number.
 *
 * A product currently maps one to one onto a source repository, and the model
 * does not assume it: several products may reference the same repository,
 * which is what a monorepo looks like from here.
 */
class Product extends NamedElement, AbstractProduct {

    refers ProductKind kind
    VersioningScheme versioningScheme = "SEMANTIC"
    /* For CALENDAR and OTHER: the pattern the versions follow, e.g. YYYY.MM.MICRO. */
    String versionPattern

    /* Where the source lives. A reference, so repositories are catalogued and scanned independently. */
    refers AbstractRepository repository

    String website

    /*
     * Path of the product root relative to the resource or workspace root.
     * Aggregator generation computes module paths from this and from the
     * location of the generated pom; nothing else uses it.
     */
    String location

    /* Where this product publishes, version independent. */
    contains Distribution[] distributions

    contains Version[] versions opposite product
}

/*
 * A reference to a product defined in another resource, so a catalog can list
 * a product it does not own.
 */
class ProductReference extends AbstractProduct {
    refers Product target
}

/* ===========================================================================
 * VERSIONS
 * =========================================================================== */

/*
 * A version of a product: a Maven version, an npm version, a GitHub release.
 * The unit that is planned, built, published and depended on.
 *
 * Planned versions are ordinary instances with a status of PLANNED and no
 * publications, which is what makes one model a roadmap and a record at once.
 *
 * The inherited name is a display label; version is the identity. The
 * inherited documentation carries release notes, including notes drafted by
 * an assistant and reviewed by a human before they are stored. Notes are data
 * on the version, never generated at read time.
 */
class Version extends NamedElement {

    container Product product opposite versions

    /* The authoritative version string, e.g. 1.0.0, 2026.9.0, 1.0.0-SNAPSHOT. */
    String version

    /* Parsed from version at load time for the query surface. -1 when absent or unparseable. */
    int major = "-1"
    int minor = "-1"
    int patch = "-1"
    /* SemVer pre-release identifiers, e.g. alpha.1, rc.2, SNAPSHOT. */
    String preRelease
    /* SemVer build metadata, ignored in precedence. */
    String build

    ReleaseStatus status = "PLANNED"
    /* Target date while planned, actual date once released. */
    Date date
    /* End of support: the date after which this version stops receiving fixes. */
    Date endOfSupport

    /* The coordinated release this version is planned for or was shipped in. */
    refers AbstractRelease release

    /* The VCS tag, e.g. v1.0.0. Absent until tagged. */
    String tag

    /* The previous version of the same product: the baseline for compatibility analysis. */
    refers Version previous

    /*
     * The bump this version makes over previous. Recorded, not computed here:
     * what an analyzer says it should have been lives in the compatibility
     * assessments below, and a disagreement between the two is a finding.
     */
    ChangeLevel changeLevel

    contains Dependency[] dependencies
    contains Publication[] publications
    contains CompatibilityAssessment[] compatibility

    /*
     * Planning inputs, and explicitly lightweight. estimate is remaining
     * effort in whatever unit the estate uses, matching the work model's
     * convention; value is a relative, unitless worth of shipping this
     * version. Total effort for a dependent version and total value for a
     * provider version are queries over the dependency graph, a thin stand in
     * for what the capability model will do properly. Both give way to
     * attached work items and modeled capabilities once those are wired.
     */
    String estimate
    Double value
}

/*
 * A kind of dependency, catalogued rather than enumerated so that ecosystem
 * vocabularies coexist: Maven compile, provided, runtime and test scopes, npm
 * dependencies, devDependencies, peerDependencies and optionalDependencies,
 * and plain "needed before" planning edges that belong to no build system.
 */
class DependencyKind extends ModelElement {
    refers DependencyKind superType
    /* True when this kind participates in build ordering and effort roll-up. */
    boolean sequencing = "true"
}

/*
 * A directed edge from the containing version to a version it depends on.
 *
 * Coarse on purpose. This is the planning graph: it sequences work,
 * topologically sorts a build, and carries effort and value roll-ups. The
 * resolved artifact graph lives in the Maven model and the resolved module
 * graph in the JVM model, and neither is restated here. An edge may be
 * authored by hand or derived from one of those graphs, which is what derived
 * and source record, so a hand authored edge is never silently overwritten by
 * a scan.
 */
class Dependency extends ModelElement { // TODO - to "Related"
    refers DependencyKind kind
    /* The version depended on. May be a planned version, which is how work is sequenced before anything exists. */
    refers Version target
    /* The declared constraint, e.g. [1.0,2.0) or ^1.2.3, when it differs from the exact target. */
    String constraint
    boolean optional
    /* True when this edge was computed from a build model rather than authored. */
    boolean ^derived
    /* URI of what the edge was derived from: a pom, a module descriptor, a package manifest. */
    String source
}

/* ===========================================================================
 * SOURCE REPOSITORIES
 * =========================================================================== */

interface AbstractRepository extends ModelElement {
}

/*
 * A catalog of source repositories. Mixed into the resource root, and also
 * the base of Repository itself, which is what gives the hierarchy its depth
 * without extra classes.
 */
class RepositoryCatalog extends NamedElement, AbstractRepository {

    @Nasdanika(logicalContainment="false")
    contains AbstractRepository[] repositories keys ^id

    @Nasdanika(logicalContainment="true")
    refers derived transient volatile readonly AbstractRepository[] resolvedRepositories keys ^id get {
        val result = new org.eclipse.emf.common.util.BasicEList<AbstractRepository>()
        for (repository : repositories) {
            val resolved = resolveRepositoryReference(repository)
            if (resolved !== null) result.add(resolved)
        }
        result
    }

    op AbstractRepository resolveRepositoryReference(AbstractRepository start) {
        var AbstractRepository current = start
        val seen = new java.util.HashSet<AbstractRepository>()
        while (current instanceof RepositoryReference) {
            if (!seen.add(current)) {
                return null // cycle
            }
            current = current.target
            if (current === null) return null // dangling
        }
        return current
    }

    contains RepositoryKind[] repositoryKinds
}

/*
 * A kind of repository node. The recommended three level vocabulary:
 *
 *   forge         the hosting service: GitHub, GitLab, Bitbucket, Codeberg.
 *                 "Forge" is the established term for a source hosting
 *                 platform, and is what ForgeFed, Debian and Eclipse use.
 *   organization  the owner namespace: a GitHub organization or user, a
 *                 GitLab group. GitHub calls it an owner, GitLab a namespace.
 *   repository    the repository itself, git or otherwise.
 *
 * Kinds are a taxonomy, so "GitHub repository" can be a kind of "git
 * repository" and a query asks for either level.
 */
class RepositoryKind extends ModelElement {
    refers RepositoryKind superType
}

/*
 * A node in the repository hierarchy: a forge, an organization, or a
 * repository, distinguished by kind rather than by class. It extends the
 * catalog, so a forge contains organizations and an organization contains
 * repositories, and any level can be federated or referenced.
 *
 * Catalogued independently of products, so repositories are loaded and
 * scanned whether or not a product claims them, which is how an estate report
 * finds the repositories nobody remembered.
 */
class Repository extends RepositoryCatalog {

    refers RepositoryKind kind

    /* Browse URL, e.g. https://github.com/Nasdanika/core. */
    String url
    /* Clone URL, when it differs from the browse URL. */
    String cloneUrl
    String defaultBranch
    Visibility visibility = "PUBLIC"
    boolean archived
    boolean fork
    /* SPDX identifier, e.g. EPL-2.0. */
    String license
    /* Topics, labels or tags carried by the forge. */
    String[] topics

    /* Dated observations, so a stale scan is visible as stale rather than mistaken for fact. */
    contains RepositoryStatistics[] statistics
}

/*
 * A dated snapshot of forge reported numbers. A contained record rather than
 * attributes on Repository, because these change without anything in the
 * model changing, and an undated number is worse than no number.
 */
class RepositoryStatistics extends ModelElement {
    Date date
    /* Size in kilobytes, as forges report it. */
    long size = "-1"
    int stars = "-1"
    int forks = "-1"
    int watchers = "-1"
    int openIssues = "-1"
    int contributors = "-1"
    /* Timestamp of the most recent push, the honest measure of activity. */
    Date lastPush
    /* Primary language as detected by the forge. */
    String language
}

class RepositoryReference extends AbstractRepository {
    refers Repository target
}

/* ===========================================================================
 * REGISTRIES AND PUBLISHING
 * =========================================================================== */

interface AbstractRegistry extends ModelElement {
}

/* A catalog of publishing destinations. Mixed into the resource root. */
class RegistryCatalog extends NamedElement, AbstractRegistry {

    @Nasdanika(logicalContainment="false")
    contains AbstractRegistry[] registries keys ^id

    @Nasdanika(logicalContainment="true")
    refers derived transient volatile readonly AbstractRegistry[] resolvedRegistries keys ^id get {
        val result = new org.eclipse.emf.common.util.BasicEList<AbstractRegistry>()
        for (registry : registries) {
            val resolved = resolveRegistryReference(registry)
            if (resolved !== null) result.add(resolved)
        }
        result
    }

    op AbstractRegistry resolveRegistryReference(AbstractRegistry start) {
        var AbstractRegistry current = start
        val seen = new java.util.HashSet<AbstractRegistry>()
        while (current instanceof RegistryReference) {
            if (!seen.add(current)) {
                return null // cycle
            }
            current = current.target
            if (current === null) return null // dangling
        }
        return current
    }

    contains RegistryKind[] registryKinds
}

/*
 * A kind of registry: Maven repository, npm registry, OCI registry, NuGet
 * feed, extension marketplace. Each ecosystem has its own word for the same
 * role, and the kind catalog is where those words live.
 */
class RegistryKind extends ModelElement {
    refers RegistryKind superType
}

/*
 * A place versions are published to: Maven Central, GitHub Packages, the npm
 * registry, the Visual Studio Marketplace, Open VSX.
 *
 * Called a registry rather than a repository because repository is already
 * the source repository here. The industry is split (Maven says repository,
 * npm and OCI say registry, marketplaces say marketplace), and the supply
 * chain specifications, SPDX and CycloneDX among them, settled on registry,
 * so that is the word used.
 */
class Registry extends NamedElement, AbstractRegistry {

    refers RegistryKind kind

    /* Service base URL, e.g. https://repo1.maven.org/maven2. */
    String url
    /* Human landing page, when it differs from the service URL. */
    String website

    /*
     * The package URL type for coordinates in this registry: maven, npm, oci,
     * nuget, generic. purl is the only coordinate vocabulary that spans all of
     * these, which is what makes one release across four ecosystems
     * expressible as data rather than as prose.
     */
    String purlType

    /*
     * The identifier the publishing tool uses for this destination: a Maven
     * repository id matching a settings.xml server, an npm registry alias.
     */
    String repositoryId

    boolean snapshots
    boolean requiresSignature
}

class RegistryReference extends AbstractRegistry {
    refers Registry target
}

/*
 * A product's standing presence in a registry, under version-less
 * coordinates. The intent to publish, which is what manifest and pom
 * generation reads; the record of having published is Publication.
 */
class Distribution extends ModelElement {

    refers AbstractRegistry registry

    /*
     * Registry native coordinates without a version:
     *   maven  org.nasdanika.models:release
     *   npm    @nasdanika/release
     *   vsx    nasdanika.release
     * The purl for a given version is this plus the registry's purlType plus
     * the version, composed rather than stored.
     */
    String coordinates

    /* Packaging or artifact type where the ecosystem has one: jar, pom, vsix, tgz. */
    String packaging

    /* Landing page for the product in this registry. */
    String url
}

/*
 * The fact that one version arrived in one registry: the audit record, and
 * the input to size and footprint reporting.
 */
class Publication extends ModelElement {
    refers Distribution distribution
    Date date
    /* URL of the published artifact or its page. */
    String url
    /* Published size in bytes. Transitive size is a query over dependencies. */
    long size = "-1"
    /* Checksum of the published artifact, algorithm prefixed, e.g. sha256:... */
    String digest
    boolean signed
}

/* ===========================================================================
 * RELEASES
 * =========================================================================== */

interface AbstractRelease extends ModelElement {
}

/* A catalog of releases. Mixed into the resource root. */
class ReleaseCatalog extends NamedElement, AbstractRelease {

    @Nasdanika(logicalContainment="false")
    contains AbstractRelease[] releases keys ^id

    @Nasdanika(logicalContainment="true")
    refers derived transient volatile readonly AbstractRelease[] resolvedReleases keys ^id get {
        val result = new org.eclipse.emf.common.util.BasicEList<AbstractRelease>()
        for (release : releases) {
            val resolved = resolveReleaseReference(release)
            if (resolved !== null) result.add(resolved)
        }
        result
    }

    op AbstractRelease resolveReleaseReference(AbstractRelease start) {
        var AbstractRelease current = start
        val seen = new java.util.HashSet<AbstractRelease>()
        while (current instanceof ReleaseReference) {
            if (!seen.add(current)) {
                return null // cycle
            }
            current = current.target
            if (current === null) return null // dangling
        }
        return current
    }
}

/*
 * A coordinated release: a set of product versions shipped together. A
 * release train in Spring and Quarkus terms, a simultaneous release in
 * Eclipse terms.
 *
 * Membership is not contained here. Versions point at the release, following
 * the Eclipse contribution model where each project declares the exact
 * version it contributes, so the train is assembled rather than edited. The
 * versions feature below derives membership, including through release
 * references from federated resources.
 *
 * The inherited documentation carries the aggregate release notes.
 */
class Release extends NamedElement, AbstractRelease {

    /* The release version, calendar versioned by convention, e.g. 2026.9.0. */
    String version
    VersioningScheme versioningScheme = "CALENDAR"

    ReleaseStatus status = "PLANNED"
    /* Target date while planned, actual date once released. */
    Date date
    /* End of life for the release as a whole. */
    Date endOfLife

    refers Release previous

    contains Milestone[] milestones

    /*
     * Versions contributed to this release, directly and through release
     * references. Derived, because the authoritative edge points the other
     * way.
     */
    refers derived transient volatile readonly Version[] versions get {
        val result = new org.eclipse.emf.common.util.BasicEList<Version>()
        for (referrer : getReferrers(ReleasePackage.Literals.VERSION__RELEASE)) {
            if (referrer instanceof Version) result.add(referrer)
        }
        for (referrer : getReferrers(ReleasePackage.Literals.RELEASE_REFERENCE__TARGET)) {
            if (referrer instanceof ReleaseReference) {
                for (indirect : referrer.getReferrers(ReleasePackage.Literals.VERSION__RELEASE)) {
                    if (indirect instanceof Version) result.add(indirect)
                }
            }
        }
        result
    }
}

/*
 * A dated checkpoint inside a release: M1, M2, RC1, GA. Eclipse runs three
 * milestones and two release candidates before a simultaneous release, and a
 * roadmap without checkpoints is a date with nothing behind it.
 */
class Milestone extends NamedElement {
    Date date
    /* The version qualifier produced at this checkpoint, e.g. M1, RC1. */
    String qualifier
    boolean reached
}

class ReleaseReference extends AbstractRelease {
    refers Release target
}

/* ===========================================================================
 * COMPATIBILITY EVIDENCE
 * =========================================================================== */

/*
 * The verdict of a compatibility analyzer comparing this version against a
 * baseline, with the incompatibilities that produced it.
 *
 * Recorded, never computed here. japicmp and Revapi do this for the JVM,
 * Eclipse API Tools for bundles, api-extractor for TypeScript, and
 * cargo-semver-checks is the exemplar of doing it well. The analysis is a
 * supplier; the record is the asset, and the asset is what makes "why was
 * this a major release" answerable a year later.
 */
class CompatibilityAssessment extends ModelElement {

    /* The version compared against, normally the containing version's previous. */
    refers Version baseline

    /* The analyzer: japicmp, revapi, api-tools, api-extractor. */
    String tool
    String toolVersion
    Date date

    /* The bump the analyzer says is required. Compare with the version's changeLevel. */
    ChangeLevel requiredBump

    boolean binaryCompatible = "true"
    boolean sourceCompatible = "true"

    /* URI of the full report, which stays outside the model. */
    String report

    contains Incompatibility[] incompatibilities
}

/*
 * One breaking change, attached to the element that broke.
 *
 * elementUri is the join: a JVM model URI such as
 * jvm:method/org.nasdanika.Foo#bar(Ljava/lang/String;)I resolves to the
 * method itself, so "this release is major because these three methods were
 * removed" is navigable rather than narrated. The same
 * observation-attachment pattern as coverage, telemetry and rule violations.
 */
class Incompatibility extends ModelElement {
    /* URI of the affected element in a structural model. */
    String elementUri
    /* The analyzer's own change code, e.g. METHOD_REMOVED, CLASS_NOW_FINAL. */
    String change
    boolean binaryIncompatible
    boolean sourceIncompatible
    /* Free text from the analyzer, kept verbatim. */
    String detail
}

/* ===========================================================================
 * GENERATION
 * =========================================================================== */

/*
 * Bill of materials, per the Maven meaning: a pom whose dependencyManagement
 * section pins the versions of a set of artifacts, so consumers import one
 * coordinate and stop choosing versions. Aggregator, per the Maven meaning: a
 * pom whose modules section builds a set of projects together.
 */
enum PomType {
    BOM
    AGGREGATOR
}

/*
 * A directive to generate a pom for the containing catalog. Present only on
 * catalogs meant to produce one, so containment is the marking.
 *
 * This is the one deliberately non-descriptive part of the model: it says
 * what to produce rather than what is true. It lives here because a generator
 * needs a source of truth, and splitting the directive from the structure it
 * follows would mean maintaining the structure twice.
 *
 * Nesting rule the generator follows, rather than a flag: when a child
 * catalog has a pom of the same type, the parent includes that pom instead of
 * expanding the child's products. A tower catalog with an aggregator pom is
 * one module of the all-models aggregator; a catalog without one is inlined.
 */
class PomGeneration extends ModelElement {

    PomType ^type

    String groupId
    String artifactId
    /* Absent means the release version for a BOM, and the parent's version for an aggregator. */
    String version

    /* URI of the template pom. Resolved against the resource, classpath supported. */
    String template
    /* Output URI, relative to the resource or workspace root. */
    String location

    /*
     * Optional membership predicate evaluated against each candidate product,
     * for catalogs defined by a property rather than by listing: everything
     * published to Maven Central, everything of a given kind. Absent means
     * every product in the catalog.
     */
    contains Evaluator filter
}
