-- SPDX-FileCopyrightText: 2026 Alexandra de Wit
--
-- SPDX-License-Identifier: MIT

{- | The ecosystem tag, the shared vocabulary the rest of the system dispatches on. The
package vocabulary keys a @PackageName@ by it, the version engine selects a per-ecosystem
parser by it, the registry adapters dispatch on it, and configuration keys a mount by it.

It sits in its own module to break the import cycle between "Ecluse.Core.Package" (whose
@PackageDetails@ holds a @Version@) and "Ecluse.Core.Version" (whose parsers dispatch on
the ecosystem).
-}
module Ecluse.Core.Ecosystem (
    Ecosystem (..),
    ecosystemName,
    parseEcosystem,
    prefixFor,
) where

import Data.Universe.Class (Universe (..))
import Data.Universe.Generic (universeGeneric)

import Ecluse.Core.Wire (WireVocab (..), lookupWire, renderWire)

-- | The package ecosystem an identity, version, or snapshot belongs to.
data Ecosystem
    = Npm
    | PyPI
    | RubyGems
    deriving stock (Ecosystem -> Ecosystem -> Bool
(Ecosystem -> Ecosystem -> Bool)
-> (Ecosystem -> Ecosystem -> Bool) -> Eq Ecosystem
forall a. (a -> a -> Bool) -> (a -> a -> Bool) -> Eq a
$c== :: Ecosystem -> Ecosystem -> Bool
== :: Ecosystem -> Ecosystem -> Bool
$c/= :: Ecosystem -> Ecosystem -> Bool
/= :: Ecosystem -> Ecosystem -> Bool
Eq, (forall x. Ecosystem -> Rep Ecosystem x)
-> (forall x. Rep Ecosystem x -> Ecosystem) -> Generic Ecosystem
forall x. Rep Ecosystem x -> Ecosystem
forall x. Ecosystem -> Rep Ecosystem x
forall a.
(forall x. a -> Rep a x) -> (forall x. Rep a x -> a) -> Generic a
$cfrom :: forall x. Ecosystem -> Rep Ecosystem x
from :: forall x. Ecosystem -> Rep Ecosystem x
$cto :: forall x. Rep Ecosystem x -> Ecosystem
to :: forall x. Rep Ecosystem x -> Ecosystem
Generic, Eq Ecosystem
Eq Ecosystem =>
(Ecosystem -> Ecosystem -> Ordering)
-> (Ecosystem -> Ecosystem -> Bool)
-> (Ecosystem -> Ecosystem -> Bool)
-> (Ecosystem -> Ecosystem -> Bool)
-> (Ecosystem -> Ecosystem -> Bool)
-> (Ecosystem -> Ecosystem -> Ecosystem)
-> (Ecosystem -> Ecosystem -> Ecosystem)
-> Ord Ecosystem
Ecosystem -> Ecosystem -> Bool
Ecosystem -> Ecosystem -> Ordering
Ecosystem -> Ecosystem -> Ecosystem
forall a.
Eq a =>
(a -> a -> Ordering)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> Bool)
-> (a -> a -> a)
-> (a -> a -> a)
-> Ord a
$ccompare :: Ecosystem -> Ecosystem -> Ordering
compare :: Ecosystem -> Ecosystem -> Ordering
$c< :: Ecosystem -> Ecosystem -> Bool
< :: Ecosystem -> Ecosystem -> Bool
$c<= :: Ecosystem -> Ecosystem -> Bool
<= :: Ecosystem -> Ecosystem -> Bool
$c> :: Ecosystem -> Ecosystem -> Bool
> :: Ecosystem -> Ecosystem -> Bool
$c>= :: Ecosystem -> Ecosystem -> Bool
>= :: Ecosystem -> Ecosystem -> Bool
$cmax :: Ecosystem -> Ecosystem -> Ecosystem
max :: Ecosystem -> Ecosystem -> Ecosystem
$cmin :: Ecosystem -> Ecosystem -> Ecosystem
min :: Ecosystem -> Ecosystem -> Ecosystem
Ord, Int -> Ecosystem -> ShowS
[Ecosystem] -> ShowS
Ecosystem -> String
(Int -> Ecosystem -> ShowS)
-> (Ecosystem -> String)
-> ([Ecosystem] -> ShowS)
-> Show Ecosystem
forall a.
(Int -> a -> ShowS) -> (a -> String) -> ([a] -> ShowS) -> Show a
$cshowsPrec :: Int -> Ecosystem -> ShowS
showsPrec :: Int -> Ecosystem -> ShowS
$cshow :: Ecosystem -> String
show :: Ecosystem -> String
$cshowList :: [Ecosystem] -> ShowS
showList :: [Ecosystem] -> ShowS
Show)

instance Universe Ecosystem where universe :: [Ecosystem]
universe = [Ecosystem]
forall a. (Generic a, GUniverse (Rep a)) => [a]
universeGeneric

instance WireVocab Ecosystem where
    wireKind :: Text
wireKind = Text
"ecosystem"
    wireTable :: NonEmpty (Ecosystem, Text)
wireTable =
        (Ecosystem
Npm, Text
"npm")
            (Ecosystem, Text)
-> [(Ecosystem, Text)] -> NonEmpty (Ecosystem, Text)
forall a. a -> [a] -> NonEmpty a
:| [ (Ecosystem
PyPI, Text
"pypi")
               , (Ecosystem
RubyGems, Text
"rubygems")
               ]

{- | The canonical wire\/config name of an ecosystem: the key an operator writes a
@mounts@ object under, and the inverse of 'parseEcosystem'.

>>> ecosystemName Npm
"npm"
-}
ecosystemName :: Ecosystem -> Text
ecosystemName :: Ecosystem -> Text
ecosystemName = Ecosystem -> Text
forall a. (WireVocab a, Eq a) => a -> Text
renderWire

{- | Parse an 'Ecosystem' from its wire name, 'Nothing' for one the build does not
serve. The config decoder reads the document's @mounts@ keys with it, and rejects an
unknown key loudly rather than skipping it (see "Ecluse.Config").

>>> parseEcosystem "npm"
Just Npm

>>> parseEcosystem "cargo"
Nothing
-}
parseEcosystem :: Text -> Maybe Ecosystem
parseEcosystem :: Text -> Maybe Ecosystem
parseEcosystem = Text -> Maybe Ecosystem
forall a. WireVocab a => Text -> Maybe a
lookupWire

{- | The path prefix a mount serves under, derived from its ecosystem and never
operator-configured, so it can neither collide nor be mistyped. A root mount is unrepresentable.

>>> prefixFor Npm
"npm" :| []
-}
prefixFor :: Ecosystem -> NonEmpty Text
prefixFor :: Ecosystem -> NonEmpty Text
prefixFor Ecosystem
eco = Ecosystem -> Text
ecosystemName Ecosystem
eco Text -> [Text] -> NonEmpty Text
forall a. a -> [a] -> NonEmpty a
:| []