module Unison.Merge.FindConflictedAlias
  ( findConflictedAlias,
  )
where

import Data.Map.Strict qualified as Map
import Data.Set qualified as Set
import Unison.Merge.DiffOp (DiffOp (..))
import Unison.Merge.Updated qualified
import Unison.Prelude
import Unison.Util.BiMultimap (BiMultimap)
import Unison.Util.BiMultimap qualified as BiMultimap
import Unison.Util.Defn (Defn (..))
import Unison.Util.Defns (Defns (..), DefnsF3)

-- @findConflictedAlias namespace diff@, given an old namespace and a diff to a new namespace, will return the first
-- "conflicted alias" encountered (if any), where a "conflicted alias" is a pair of names that referred to the same
-- thing in the old namespace, but different things in the new one.
--
-- For example, if the old namespace was
--
--   foo = #foo
--   bar = #foo
--
-- and the new namespace is
--
--   foo = #baz
--   bar = #qux
--
-- then (foo, bar) is a conflicted alias.
findConflictedAlias ::
  forall name synhashed term typ.
  (Ord name, forall ref. Eq (synhashed ref), Ord term, Ord typ) =>
  Defns (BiMultimap term name) (BiMultimap typ name) ->
  DefnsF3 (Map name) DiffOp synhashed term typ ->
  Maybe (Defn (name, name) (name, name))
findConflictedAlias :: forall name (synhashed :: * -> *) term typ.
(Ord name, forall ref. Eq (synhashed ref), Ord term, Ord typ) =>
Defns (BiMultimap term name) (BiMultimap typ name)
-> DefnsF3 (Map name) DiffOp synhashed term typ
-> Maybe (Defn (name, name) (name, name))
findConflictedAlias Defns (BiMultimap term name) (BiMultimap typ name)
defns DefnsF3 (Map name) DiffOp synhashed term typ
diff =
  [Maybe (Defn (name, name) (name, name))]
-> Maybe (Defn (name, name) (name, name))
forall (t :: * -> *) (f :: * -> *) a.
(Foldable t, Alternative f) =>
t (f a) -> f a
asum [(name, name) -> Defn (name, name) (name, name)
forall term typ. term -> Defn term typ
TermDefn ((name, name) -> Defn (name, name) (name, name))
-> Maybe (name, name) -> Maybe (Defn (name, name) (name, name))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> BiMultimap term name
-> Map name (DiffOp (synhashed term)) -> Maybe (name, name)
forall ref.
(Eq (synhashed ref), Ord ref) =>
BiMultimap ref name
-> Map name (DiffOp (synhashed ref)) -> Maybe (name, name)
go Defns (BiMultimap term name) (BiMultimap typ name)
defns.terms DefnsF3 (Map name) DiffOp synhashed term typ
diff.terms, (name, name) -> Defn (name, name) (name, name)
forall term typ. typ -> Defn term typ
TypeDefn ((name, name) -> Defn (name, name) (name, name))
-> Maybe (name, name) -> Maybe (Defn (name, name) (name, name))
forall (f :: * -> *) a b. Functor f => (a -> b) -> f a -> f b
<$> BiMultimap typ name
-> Map name (DiffOp (synhashed typ)) -> Maybe (name, name)
forall ref.
(Eq (synhashed ref), Ord ref) =>
BiMultimap ref name
-> Map name (DiffOp (synhashed ref)) -> Maybe (name, name)
go Defns (BiMultimap term name) (BiMultimap typ name)
defns.types DefnsF3 (Map name) DiffOp synhashed term typ
diff.types]
  where
    go ::
      forall ref.
      (Eq (synhashed ref), Ord ref) =>
      BiMultimap ref name ->
      Map name (DiffOp (synhashed ref)) ->
      Maybe (name, name)
    go :: forall ref.
(Eq (synhashed ref), Ord ref) =>
BiMultimap ref name
-> Map name (DiffOp (synhashed ref)) -> Maybe (name, name)
go BiMultimap ref name
namespace Map name (DiffOp (synhashed ref))
diff =
      [Maybe (name, name)] -> Maybe (name, name)
forall (t :: * -> *) (f :: * -> *) a.
(Foldable t, Alternative f) =>
t (f a) -> f a
asum (((name, DiffOp (synhashed ref)) -> Maybe (name, name))
-> [(name, DiffOp (synhashed ref))] -> [Maybe (name, name)]
forall a b. (a -> b) -> [a] -> [b]
map (name, DiffOp (synhashed ref)) -> Maybe (name, name)
f (Map name (DiffOp (synhashed ref))
-> [(name, DiffOp (synhashed ref))]
forall k a. Map k a -> [(k, a)]
Map.toList Map name (DiffOp (synhashed ref))
diff))
      where
        f :: (name, DiffOp (synhashed ref)) -> Maybe (name, name)
        f :: (name, DiffOp (synhashed ref)) -> Maybe (name, name)
f (name
name, DiffOp (synhashed ref)
op) =
          case DiffOp (synhashed ref)
op of
            DiffOp'Add synhashed ref
_ -> Maybe (name, name)
forall a. Maybe a
Nothing
            DiffOp'Delete synhashed ref
_ -> Maybe (name, name)
forall a. Maybe a
Nothing
            DiffOp'Update Updated (synhashed ref)
hashed1 ->
              name -> BiMultimap ref name -> Set name
forall a b. (Ord a, Ord b) => b -> BiMultimap a b -> Set b
BiMultimap.lookupPreimage name
name BiMultimap ref name
namespace
                Set name -> (Set name -> Set name) -> Set name
forall a b. a -> (a -> b) -> b
& name -> Set name -> Set name
forall a. Ord a => a -> Set a -> Set a
Set.delete name
name
                Set name -> (Set name -> [name]) -> [name]
forall a b. a -> (a -> b) -> b
& Set name -> [name]
forall a. Set a -> [a]
Set.toList
                [name] -> ([name] -> [Maybe (name, name)]) -> [Maybe (name, name)]
forall a b. a -> (a -> b) -> b
& (name -> Maybe (name, name)) -> [name] -> [Maybe (name, name)]
forall a b. (a -> b) -> [a] -> [b]
map (synhashed ref -> name -> Maybe (name, name)
g Updated (synhashed ref)
hashed1.new)
                [Maybe (name, name)]
-> ([Maybe (name, name)] -> Maybe (name, name))
-> Maybe (name, name)
forall a b. a -> (a -> b) -> b
& [Maybe (name, name)] -> Maybe (name, name)
forall (t :: * -> *) (f :: * -> *) a.
(Foldable t, Alternative f) =>
t (f a) -> f a
asum
          where
            g :: synhashed ref -> name -> Maybe (name, name)
            g :: synhashed ref -> name -> Maybe (name, name)
g synhashed ref
hashed1 name
alias =
              case name
-> Map name (DiffOp (synhashed ref))
-> Maybe (DiffOp (synhashed ref))
forall k a. Ord k => k -> Map k a -> Maybe a
Map.lookup name
alias Map name (DiffOp (synhashed ref))
diff of
                Just (DiffOp'Update Updated (synhashed ref)
hashed2) | synhashed ref
hashed1 synhashed ref -> synhashed ref -> Bool
forall a. Eq a => a -> a -> Bool
== Updated (synhashed ref)
hashed2.new -> Maybe (name, name)
forall a. Maybe a
Nothing
                -- If "foo" was updated but its alias "bar" was deleted, that's ok
                Just (DiffOp'Delete synhashed ref
_) -> Maybe (name, name)
forall a. Maybe a
Nothing
                Maybe (DiffOp (synhashed ref))
_ -> (name, name) -> Maybe (name, name)
forall a. a -> Maybe a
Just (name
name, name
alias)