content: {-# LANGUAGE OverloadedStrings #-}
  
  module Reporting.Error.Import
    ( Error (..),
      Problem (..),
      toReport,
    )
  where
  
  import Data.Map qualified as Map
  import Data.Set qualified as Set
  import Gren.ModuleName qualified as ModuleName
  import Gren.Package qualified as Pkg
  import Reporting.Annotation qualified as A
  import Reporting.Doc qualified as D
  import Reporting.Render.Code qualified as Code
  import Reporting.Report qualified as Report
  import Reporting.Suggest qualified as Suggest
  
  -- ERROR
  
  data Error = Error
    { _region :: A.Region,
      _import :: ModuleName.Raw,
      _unimported :: Set.Set ModuleName.Raw,
      _problem :: Problem
    }
  
  data Problem
    = NotFound
    | Ambiguous FilePath [FilePath] Pkg.Name [Pkg.Name]
    | AmbiguousLocal FilePath FilePath [FilePath]
    | AmbiguousForeign Pkg.Name Pkg.Name [Pkg.Name]
  
  -- TO REPORT
  
  toReport :: Code.Source -> Error -> Report.Report
  toReport source (Error region name unimportedModules problem) =
    case problem of
      NotFound ->
        Report.Report "MODULE NOT FOUND" region [] $
          Code.toSnippet
            source
            region
            Nothing
            ( D.reflow $
                "You are trying to import a `" ++ ModuleName.toChars name ++ "` module:",
              D.stack
                [ D.reflow $
                    "I checked the \"dependencies\" and \"source-directories\" listed in your gren.json,\
                    \ but I cannot find it! Maybe it is a typo for one of these names?",
                  D.dullyellow $
                    D.indent 4 $
                      D.vcat $
                        map D.fromName (toSuggestions name unimportedModules),
                  case Map.lookup name Pkg.suggestions of
                    Nothing ->
                      D.toSimpleHint $
                        "If it is not a typo, check the \"dependencies\" and \"source-directories\"\
                        \ of your gren.json to make sure all the packages you need are listed there!"
                    Just dependency ->
                      D.toFancyHint
                        [ "Maybe",
                          "you",
                          "want",
                          "the",
                          "`" <> D.fromName name <> "`",
                          "module",
                          "defined",
                          "in",
                          "the",
                          D.fromChars (Pkg.toChars dependency),
                          "package?",
                          "Running",
                          D.green (D.fromChars ("gren package install " ++ Pkg.toChars dependency)),
                          "should",
                          "make",
                          "it",
                          "available!"
                        ]
                ]
            )
      Ambiguous path _ pkg _ ->
        Report.Report "AMBIGUOUS IMPORT" region [] $
          Code.toSnippet
            source
            region
            Nothing
            ( D.reflow $
                "You are trying to import a `" ++ ModuleName.toChars name ++ "` module:",
              D.stack
                [ D.fillSep $
                    [ "But",
                      "I",
                      "found",
                      "multiple",
                      "modules",
                      "with",
                      "that",
                      "name.",
                      "One",
                      "in",
                      "the",
                      D.dullyellow (D.fromChars (Pkg.toChars pkg)),
                      "package,",
                      "and",
                      "another",
                      "defined",
                      "locally",
                      "in",
                      "the",
                      D.dullyellow (D.fromChars path),
                      "file.",
                      "I",
                      "do",
                      "not",
                      "have",
                      "a",
                      "way",
                      "to",
                      "choose",
                      "between",
                      "them."
                    ],
                  D.reflow $
                    "Try changing the name of the locally defined module to clear up the ambiguity?"
                ]
            )
      AmbiguousLocal path1 path2 paths ->
        Report.Report "AMBIGUOUS IMPORT" region [] $
          Code.toSnippet
            source
            region
            Nothing
            ( D.reflow $
                "You are trying to import a `" ++ ModuleName.toChars name ++ "` module:",
              D.stack
                [ D.reflow $
                    "But I found multiple files in your \"source-directories\" with that name:",
                  D.dullyellow $
                    D.indent 4 $
                      D.vcat $
                        map D.fromChars (path1 : path2 : paths),
                  D.reflow $
                    "Change the module names to be distinct!"
                ]
            )
      AmbiguousForeign pkg1 pkg2 pkgs ->
        Report.Report "AMBIGUOUS IMPORT" region [] $
          Code.toSnippet
            source
            region
            Nothing
            ( D.reflow $
                "You are trying to import a `" ++ ModuleName.toChars name ++ "` module:",
              D.stack
                [ D.reflow $
                    "But multiple packages in your \"dependencies\" that expose a module that name:",
                  D.dullyellow $
                    D.indent 4 $
                      D.vcat $
                        map (D.fromChars . Pkg.toChars) (pkg1 : pkg2 : pkgs),
                  D.reflow $
                    "There is no way to disambiguate in cases like this right now. Of the known name\
                    \ clashes, they are usually for packages with similar purposes, so the current\
                    \ recommendation is to pick just one of them.",
                  D.toSimpleNote $
                    "It seems possible to resolve this with new syntax in imports, but that is\
                    \ more complicated than it sounds. Right now, our module names are tied to GitHub\
                    \ repos, but we may want to get rid of that dependency for a variety of reasons.\
                    \ That would in turn have implications for our package infrastructure, hosting\
                    \ costs, and possibly on how package names are specified. The particular syntax\
                    \ chosen seems like it would interact with all these factors in ways that are\
                    \ difficult to predict, potentially leading to harder problems later on. So more\
                    \ design work and planning is needed on these topics."
                ]
            )
  
  toSuggestions :: ModuleName.Raw -> Set.Set ModuleName.Raw -> [ModuleName.Raw]
  toSuggestions name unimportedModules =
    take 4 $
      Suggest.sort (ModuleName.toChars name) ModuleName.toChars (Set.toList unimportedModules)
  