{-
-- EPITECH PROJECT, 2025
-- Glados
-- File description:
-- Error Formatter
-}

{-|
Module      : Common.Error
Description : Error formatting utilities for the Glados compiler.
Stability   : stable

This module provides tools to transform technical Megaparsec errors into
human-readable, colored error messages. It helps standardize error reporting
across the entire application (Parsing, Semantics, Imports).
-}
module Common.Error (formatParseError, formatError, formatFancyError, getExpected, getUnexpected) where

import Data.Text (Text)
import Data.Void (Void)

-- Imports specific to Position handling
import Text.Megaparsec
    ( PosState(..)
    , reachOffset
    , unPos
    , sourceLine
    , sourceColumn
    )

-- Imports for Error types (ParseError, Bundle, etc.)
import Text.Megaparsec.Error
import qualified Data.List.NonEmpty as NE
import Data.List.NonEmpty (NonEmpty(..))
import qualified Data.Set as Set
import Data.List (intercalate, sort)

-- | Formats a generic error message with a colored tag.
--
-- @args
--   - tag: The error category (e.g., "Semantic Error", "Import Error").
--   - msg: The error description.
--
-- @return
--   A formatted string with the tag in red ANSI color.
formatError :: String -> String -> String
formatError :: [Char] -> [Char] -> [Char]
formatError [Char]
tag [Char]
msg = [Char]
"\x1b[31m[" [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
tag [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
"]\x1b[0m " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
msg

-- | Extracts the "expected" tokens from a ParseError.
--
-- @args
--   - err: The Megaparsec error object.
--
-- @return
--   A clean string listing expected tokens or "custom error".
getExpected :: ParseError Text Void -> String
getExpected :: ParseError Text Void -> [Char]
getExpected (TrivialError Int
_ Maybe (ErrorItem (Token Text))
_ Set (ErrorItem (Token Text))
expectedSet) = 
    [ErrorItem Char] -> [Char]
cleanExpected ([ErrorItem Char] -> [ErrorItem Char]
forall a. Ord a => [a] -> [a]
sort ([ErrorItem Char] -> [ErrorItem Char])
-> [ErrorItem Char] -> [ErrorItem Char]
forall a b. (a -> b) -> a -> b
$ Set (ErrorItem Char) -> [ErrorItem Char]
forall a. Set a -> [a]
Set.toList Set (ErrorItem Char)
Set (ErrorItem (Token Text))
expectedSet)
getExpected (FancyError Int
_ Set (ErrorFancy Void)
_) = [Char]
"custom error"

-- | Extracts the "unexpected" token from a ParseError.
--
-- @args
--   - err: The Megaparsec error object.
--
-- @return
--   A string describing what was found instead (e.g., "end of input", "\"foo\"").
getUnexpected :: ParseError Text Void -> String
getUnexpected :: ParseError Text Void -> [Char]
getUnexpected (TrivialError Int
_ (Just ErrorItem (Token Text)
unex) Set (ErrorItem (Token Text))
_) = ErrorItem Char -> [Char]
parseErrorItem ErrorItem Char
ErrorItem (Token Text)
unex
getUnexpected (TrivialError Int
_ Maybe (ErrorItem (Token Text))
Nothing Set (ErrorItem (Token Text))
_) = [Char]
"end of input"
getUnexpected (FancyError Int
_ Set (ErrorFancy Void)
_) = [Char]
"custom error"

-- | Format 'FancyError' messages (generated by 'fail').
--
-- @details
--   Iterates over the set of fancy errors and extracts the text message
--   if it exists (ErrorFail).
formatFancyError :: Set.Set (ErrorFancy Void) -> String
formatFancyError :: Set (ErrorFancy Void) -> [Char]
formatFancyError Set (ErrorFancy Void)
errs = [Char] -> [[Char]] -> [Char]
forall a. [a] -> [[a]] -> [a]
intercalate [Char]
"; " ((ErrorFancy Void -> [Char]) -> [ErrorFancy Void] -> [[Char]]
forall a b. (a -> b) -> [a] -> [b]
map ErrorFancy Void -> [Char]
forall {e}. ErrorFancy e -> [Char]
showFancy (Set (ErrorFancy Void) -> [ErrorFancy Void]
forall a. Set a -> [a]
Set.toList Set (ErrorFancy Void)
errs))
  where
    showFancy :: ErrorFancy e -> [Char]
showFancy (ErrorFail [Char]
msg) = [Char]
msg
    showFancy (ErrorIndentation Ordering
_ Pos
ref Pos
actual) = 
        [Char]
"incorrect indentation (got " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Int -> [Char]
forall a. Show a => a -> [Char]
show (Pos -> Int
unPos Pos
actual) [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ 
        [Char]
", should be " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Int -> [Char]
forall a. Show a => a -> [Char]
show (Pos -> Int
unPos Pos
ref) [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
")"
    showFancy (ErrorCustom e
_) = [Char]
"custom error"

-- | Format the specific error description (Trivial vs Fancy).
getErrorDesc :: ParseError Text Void -> String
getErrorDesc :: ParseError Text Void -> [Char]
getErrorDesc err :: ParseError Text Void
err@(TrivialError Int
_ Maybe (ErrorItem (Token Text))
_ Set (ErrorItem (Token Text))
_) =
    [Char]
", expected " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ ParseError Text Void -> [Char]
getExpected ParseError Text Void
err [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++
    [Char]
" but got " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ ParseError Text Void -> [Char]
getUnexpected ParseError Text Void
err
getErrorDesc (FancyError Int
_ Set (ErrorFancy Void)
fancyErrs) =
    [Char]
": " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Set (ErrorFancy Void) -> [Char]
formatFancyError Set (ErrorFancy Void)
fancyErrs

-- | Main entry point to format a Megaparsec error bundle.
--
-- @args
--   - bundle: The full error bundle returned by 'parse'.
--
-- @details
--   Extracts the first error, calculates position, and formats the message.
--   Refactored to meet coding style (short function, short lines).
--
formatParseError :: ParseErrorBundle Text Void -> String
formatParseError :: ParseErrorBundle Text Void -> [Char]
formatParseError ParseErrorBundle Text Void
bundle =
    let (ParseError Text Void
err :| [ParseError Text Void]
_) = ParseErrorBundle Text Void -> NonEmpty (ParseError Text Void)
forall s e. ParseErrorBundle s e -> NonEmpty (ParseError s e)
bundleErrors ParseErrorBundle Text Void
bundle
        (Int
line, Int
col) = PosState Text -> Int -> (Int, Int)
getLineCol (ParseErrorBundle Text Void -> PosState Text
forall s e. ParseErrorBundle s e -> PosState s
bundlePosState ParseErrorBundle Text Void
bundle) (ParseError Text Void -> Int
forall s e. ParseError s e -> Int
errorOffset ParseError Text Void
err)
        details :: [Char]
details = [Char]
"line " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Int -> [Char]
forall a. Show a => a -> [Char]
show Int
line [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ [Char]
", column " [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++ Int -> [Char]
forall a. Show a => a -> [Char]
show Int
col [Char] -> [Char] -> [Char]
forall a. [a] -> [a] -> [a]
++
                  ParseError Text Void -> [Char]
getErrorDesc ParseError Text Void
err
    in [Char] -> [Char] -> [Char]
formatError [Char]
"Parse Error" [Char]
details

-- | Calculates line and column numbers from a generic Position State.
--
-- @args
--   - state: The initial position state.
--   - offset: The absolute character offset of the error.
--
-- @return
--   A tuple (Line, Column).
getLineCol :: PosState Text -> Int -> (Int, Int)
getLineCol :: PosState Text -> Int -> (Int, Int)
getLineCol PosState Text
state Int
offset = 
    let (Maybe [Char]
_, PosState Text
newState) = Int -> PosState Text -> (Maybe [Char], PosState Text)
forall s.
TraversableStream s =>
Int -> PosState s -> (Maybe [Char], PosState s)
reachOffset Int
offset PosState Text
state
        pos :: SourcePos
pos = PosState Text -> SourcePos
forall s. PosState s -> SourcePos
pstateSourcePos PosState Text
newState
    in (Pos -> Int
unPos (SourcePos -> Pos
sourceLine SourcePos
pos), Pos -> Int
unPos (SourcePos -> Pos
sourceColumn SourcePos
pos))

-- | Formats a list of expected ErrorItems into a readable string.
--
-- @details
--   Prioritizes Labels (human-readable names like "semicolon") over raw Tokens.
--   If many tokens are expected, lists the first 3 or joins labels with "or".
--
cleanExpected :: [ErrorItem Char] -> String
cleanExpected :: [ErrorItem Char] -> [Char]
cleanExpected [] = [Char]
"unknown"
cleanExpected [ErrorItem Char]
items = 
    let labels :: [[Char]]
labels = [NonEmpty Char -> [Char]
forall a. NonEmpty a -> [a]
NE.toList NonEmpty Char
l | Label NonEmpty Char
l <- [ErrorItem Char]
items] 
        tokens :: [[Char]]
tokens = [[Char] -> [Char]
forall a. Show a => a -> [Char]
show (NonEmpty Char -> [Char]
forall a. NonEmpty a -> [a]
NE.toList NonEmpty Char
t) | Tokens NonEmpty Char
t <- [ErrorItem Char]
items]
    in if [[Char]] -> Bool
forall a. [a] -> Bool
forall (t :: * -> *) a. Foldable t => t a -> Bool
null [[Char]]
labels 
       then [Char] -> [[Char]] -> [Char]
forall a. [a] -> [[a]] -> [a]
intercalate [Char]
", " (Int -> [[Char]] -> [[Char]]
forall a. Int -> [a] -> [a]
take Int
3 [[Char]]
tokens)
       else [Char] -> [[Char]] -> [Char]
forall a. [a] -> [[a]] -> [a]
intercalate [Char]
" or " [[Char]]
labels

-- | Converts a single ErrorItem to its string representation.
parseErrorItem :: ErrorItem Char -> String
parseErrorItem :: ErrorItem Char -> [Char]
parseErrorItem (Tokens NonEmpty Char
t) = [Char] -> [Char]
forall a. Show a => a -> [Char]
show (NonEmpty Char -> [Char]
forall a. NonEmpty a -> [a]
NE.toList NonEmpty Char
t)
parseErrorItem (Label NonEmpty Char
l) = NonEmpty Char -> [Char]
forall a. NonEmpty a -> [a]
NE.toList NonEmpty Char
l
parseErrorItem (ErrorItem Char
EndOfInput) = [Char]
"end of input"