{-# LANGUAGE CPP #-}
{-# LANGUAGE TypeApplications #-}

module Formatting (tests) where

import Control.Exception
import Data.Aeson (SumEncoding(UntaggedValue), defaultOptions, sumEncoding, tagSingleConstructors)
import Data.Aeson.TypeScript.TH
import Data.Proxy
import Data.String.Interpolate
import Test.Hspec


data D = S | F deriving (Eq, Show)
$(deriveTypeScript defaultOptions ''D)

data D2 = S2 | F2 deriving (Eq, Show)
$(deriveTypeScript defaultOptions ''D2)

-- A.encode U --> "[]"
data Unit = U deriving (Eq, Show)
$(deriveTypeScript defaultOptions ''Unit)

-- A.encode UTagSingle --> "\"UTagSingle\""
data UnitTagSingle = UTagSingle deriving (Eq, Show)
$(deriveTypeScript (defaultOptions { tagSingleConstructors = True, sumEncoding = UntaggedValue }) ''UnitTagSingle)

data PrimeInType' = PrimeInType
$(deriveTypeScript defaultOptions ''PrimeInType')

data PrimeInConstr = PrimeInConstr'
$(deriveTypeScript defaultOptions ''PrimeInConstr)

data FooBar =
  Foo {
    -- | @no-emit-typescript
    recordString :: String
    , recordInt :: Int
    }
  |
  -- | @no-emit-typescript
  Bar {
      barInt :: Int
  }
$(deriveTypeScript defaultOptions ''FooBar)

data NormalConstructors =
  -- | @no-emit-typescript
  Con1 String
  | Con2 Int
$(deriveTypeScript defaultOptions ''NormalConstructors)

tests :: Spec
tests = describe "Formatting" $ do
  describe "when given a Sum Type" $ do
    describe "and the TypeAlias format option is set" $
      it "should generate a TS string literal type" $
        formatTSDeclarations' defaultFormattingOptions (getTypeScriptDeclarations @D Proxy) `shouldBe`
          [i|type D = "S" | "F";|]

    describe "and the Enum format option is set" $ do
      it "should generate a TS Enum" $
        formatTSDeclarations' (defaultFormattingOptions { typeAlternativesFormat = Enum }) (getTypeScriptDeclarations @D Proxy) `shouldBe`
          [i|enum D { S="S", F="F" }|]

      it "should generate a TS Enum with multiple" $
        formatTSDeclarations' (defaultFormattingOptions { typeAlternativesFormat = Enum }) (getTypeScriptDeclarations @D Proxy <> getTypeScriptDeclarations @D2 Proxy) `shouldBe`
          [__i|enum D { S="S", F="F" }

               enum D2 { S2="S2", F2="F2" }|]

      it "should generate a normal type from Unit, singe tagSingleConstructors=False by default" $
        formatTSDeclarations' (defaultFormattingOptions { typeAlternativesFormat = Enum }) (getTypeScriptDeclarations @Unit Proxy) `shouldBe`
          [__i|type Unit = IU;

               type IU = void[];|]

      it "should generate a suitable enum from UnitTagSingle" $
        formatTSDeclarations' (defaultFormattingOptions { typeAlternativesFormat = Enum }) (getTypeScriptDeclarations @UnitTagSingle Proxy) `shouldBe`
          [__i|enum UnitTagSingle { UTagSingle="UTagSingle" }|]

    describe "and the EnumWithType format option is set" $ do
      it "should generate a TS Enum with a type declaration" $
        formatTSDeclarations' (defaultFormattingOptions { typeAlternativesFormat = EnumWithType }) (getTypeScriptDeclarations @D Proxy) `shouldBe`
          [i|enum DEnum { S="S", F="F" }\n\ntype D = keyof typeof DEnum;|]

      it "should also work for UnitTagSingle" $
        formatTSDeclarations' (defaultFormattingOptions { typeAlternativesFormat = EnumWithType }) (getTypeScriptDeclarations @UnitTagSingle Proxy) `shouldBe`
          [i|enum UnitTagSingleEnum { UTagSingle="UTagSingle" }\n\ntype UnitTagSingle = keyof typeof UnitTagSingleEnum;|]

  describe "when the name has an apostrophe" $ do
    describe "in the type" $ do
      it "throws an error" $ do
        evaluate (formatTSDeclarations' defaultFormattingOptions (getTypeScriptDeclarations @PrimeInType' Proxy)) `shouldThrow` anyErrorCall

    describe "in the constructor" $ do
      it "throws an error" $ do
        evaluate (formatTSDeclarations' defaultFormattingOptions (getTypeScriptDeclarations @PrimeInConstr Proxy)) `shouldThrow` anyErrorCall

#if MIN_VERSION_template_haskell(2,18,0)
  describe "when @no-emit-typescript is present" $ do
    it [i|works on records and constructors of record types|] $ do
      formatTSDeclarations' defaultFormattingOptions (getTypeScriptDeclarations @FooBar Proxy) `shouldBe` [i|type FooBar = IFoo;\n\ninterface IFoo {\n  tag: "Foo";\n  recordInt: number;\n}|]

    it [i|works on normal constructors|] $ do
      formatTSDeclarations' defaultFormattingOptions (getTypeScriptDeclarations @NormalConstructors Proxy) `shouldBe` [i|type NormalConstructors = ICon2;\n\ninterface ICon2 {\n  tag: "Con2";\n  contents: number;\n}|]
#endif

main :: IO ()
main = hspec tests