content: module Nix.Diff.Transformations where
  
  import qualified Patience
  import Data.Generics.Uniplate.Data ( transformBi )
  
  import Nix.Diff.Types
  import qualified Data.Set as Set
  import Data.Text (Text)
  
  {-| In large diffs there may be a lot of derivations
      that doesn't change at all, but changed some of
      its nested inputs, that was already compared.
      This case will produce "stairs" of useless reports:
      ```
      • The input derivation named `a` differs
        - /nix/store/j1jmbxd74kzianaywml2nw1ja31a00r5-a.drv:{out}
        + /nix/store/ww51c2dha7m5l5qjzh2rblicsamkrh62-a.drv:{out}
        • The input derivation named `b` differs
          - /nix/store/j1jmbxd74kzianaywml2nw1ja31a00r5-b.drv:{out}
          + /nix/store/ww51c2dha7m5l5qjzh2rblicsamkrh62-b.drv:{out}
          • The input derivation named `c` differs
            • These two derivations have already been compared
      ```
      This transformation will fold all these subtrees of diff
      into one OnlyAlreadyComparedBelow.
  -}
  foldAlreadyComparedSubTrees :: DerivationDiff -> DerivationDiff
  foldAlreadyComparedSubTrees dd = case dd of
    DerivationsAreTheSame -> dd
    AlreadyCompared -> dd
    OnlyAlreadyComparedBelow{} -> dd
    NamesDontMatch{} -> dd
    OutputsDontMatch{} -> dd
    DerivationDiff{..} -> if
        | OutputsDiff Nothing [] <- outputsDiff
        , Nothing <- platformDiff
        , Nothing <- builderDiff
        , Nothing <- argumentsDiff
        , SourcesDiff Nothing [] <- sourcesDiff
        , InputsDiff Nothing inputs <- inputsDiff'
        , all alreadyComparedBelow inputs
        , envSkippedOrUnchanged envDiff
            -> OnlyAlreadyComparedBelow outputStructure
  
        | otherwise -> DerivationDiff
            { outputStructure
            , outputsDiff
            , platformDiff
            , builderDiff
            , argumentsDiff
            , sourcesDiff
            , inputsDiff = inputsDiff'
            , envDiff
            }
      where
        inputsDiff' = transformNestedDerivationDiffs
              foldAlreadyComparedSubTrees
              inputsDiff
  
  {-| If packages deep in the dependency graph have been changed, many other
     derivations will also change in an uninteresting manner. This can lead to
     hundreds or thousands of lines of output like this:
  
     ```
     • The input derivation named `bash-5.2p32` differs
       • These two derivations have already been compared
     • The input derivation named `ensure-newer-sources-hook` differs
       • These two derivations have already been compared
     • The input derivation named `pypa-install-hook` differs
       • These two derivations have already been compared
     ```
  
     This transformation will fold sequences of `OneDerivationDiff` like this
     into a single `ManyDerivationsAlreadyComparedDiff`.
  -}
  foldManyInputDerivationsAlreadyCompared :: DerivationDiff -> DerivationDiff
  foldManyInputDerivationsAlreadyCompared dd = case dd of
    DerivationsAreTheSame -> dd
    AlreadyCompared -> dd
    OnlyAlreadyComparedBelow{} -> dd
    NamesDontMatch{} -> dd
    OutputsDontMatch{} -> dd
    DerivationDiff{..} ->
      let inputsDiff' = transformNestedDerivationDiffs
                          foldManyInputDerivationsAlreadyCompared
                          inputsDiff
  
          helper :: [Text] -> [InputDerivationsDiff] -> [InputDerivationsDiff]
          helper [] [] = []
          helper names [] =
            [ManyDerivationsAlreadyComparedDiff
              { drvNames = Set.fromList names
              }]
          helper names (input:inputs) =
            case input of
              OneDerivationDiff
                { drvName
                , drvDiff = AlreadyCompared
                } -> helper
                      (drvName:names)
                      inputs
              OneDerivationDiff{} -> input : helper names inputs
              SomeDerivationsDiff{} -> input : helper names inputs
              ManyDerivationsAlreadyComparedDiff{} -> error "unreachable"
  
       in DerivationDiff
         { inputsDiff =
             inputsDiff'
               { inputDerivationDiffs =
                   helper [] inputsDiff'.inputDerivationDiffs
               }
         , ..
         }
  
  {-| This transformation is most useful for
      --json output, because it will sqash a lot of
      `{"content":"  ","type":"Both"},{"content":"When","type":"Both"},{"content":" ","type":"Both"},{"content":"in","type":"Both"},{"content":" ","type":"Both"}`
      into one
      `{"content":"  When in ","type":"Both"}`
      block.
  
      To understand this problem clearer, see `golden-tests/expected-outputs/json`
      and `golden-tests/expected-outputs/json-squashed`.
  
      _Warning_: this transformation can break some parts of printing in
      human readable mode.
  -}
  squashSourcesAndEnvsDiff :: DerivationDiff -> DerivationDiff
  squashSourcesAndEnvsDiff = transformBi
      \(TextDiff x) -> TextDiff (squashDiff x)
    where
      squashDiff (Patience.Old a : Patience.Old b : xs) =
        squashDiff (Patience.Old (a <> b) : xs)
      squashDiff (Patience.New a : Patience.New b : xs) =
        squashDiff (Patience.New (a <> b) : xs)
      squashDiff (Patience.Both a _ : Patience.Both b _ : xs) =
        let ab = a <> b in squashDiff (Patience.Both ab ab : xs)
      squashDiff (x : xs) = x : squashDiff xs
      squashDiff [] = []
  
  -- ** Helpers
  
  transformNestedDerivationDiffs
    :: (DerivationDiff -> DerivationDiff)
    -> InputsDiff
    -> InputsDiff
  transformNestedDerivationDiffs f InputsDiff{..} = InputsDiff
    { inputExtraNames
    , inputDerivationDiffs = map changeDerivation inputDerivationDiffs
    }
    where
      changeDerivation idd = case idd of
        OneDerivationDiff name dd ->
          OneDerivationDiff name (f dd)
        SomeDerivationsDiff {} -> idd
        ManyDerivationsAlreadyComparedDiff {} -> idd
  
  envSkippedOrUnchanged :: Maybe EnvironmentDiff -> Bool
  envSkippedOrUnchanged = \case
    Nothing -> True
    Just EnvironmentsAreEqual -> True
    _ -> False
  
  alreadyComparedBelow :: InputDerivationsDiff -> Bool
  alreadyComparedBelow = \case
    OneDerivationDiff _ AlreadyCompared -> True
    OneDerivationDiff _ OnlyAlreadyComparedBelow{} -> True
    _ -> False
  
  transformIf :: Bool -> (DerivationDiff -> DerivationDiff) -> DerivationDiff -> DerivationDiff
  transformIf False _ = id
  transformIf True f = f
  