module GeneratorSpec where

import TestImport
import qualified Data.Map as M
import Data.List.NonEmpty (NonEmpty(..))

type UserId = Int
type NonEmptyUserId = NonEmpty UserId

resources :: [ResourceTree String]
resources = [parseRoutes|
  /api ApiP:
    /users/#UserId UsersR GET
    /users/#Int/foo UsersFooR GET
    /users/#NonEmptyUserId/bar UsersBarR GET
    /users/#Other/baz UsersBazR GET
|]

spec :: Spec
spec = do
  describe "genFlowClasses" $ do
    it "should generate classes" $ do
      let classes = genFlowClasses M.empty [] [] resources
      map className classes
        `shouldBe`
          [ "PATHS_TYPE_paths"
          , "PATHS_TYPE_paths_static_pages"
          , "PATHS_TYPE_paths_api"
          ]

    it "should identify path variable types" $ do
      let
        classes = genFlowClasses M.empty [] [] resources
        apiClass = find ((== "PATHS_TYPE_paths_api") . className) classes
      map classMembers apiClass
        `shouldBe`
          Just
            [ Method
              "users"
              [ Path "api"
              , Path "users"
              , Dyn NumberT
              ]
            , Method
              "users_bar"
              [ Path "api"
              , Path "users"
              , Dyn (NonEmptyT NumberT)
              , Path "bar"
              ]
            , Method
              "users_baz"
              [ Path "api"
              , Path "users"
              , Dyn StringT
              , Path "baz"
              ]
            , Method
              "users_foo"
              [ Path "api"
              , Path "users"
              , Dyn NumberT
              , Path "foo"
              ]
            ]
    it "should respect overrides " $ do
      let
        classes = genFlowClasses (M.fromList [("UserId", StringT)]) [] [] resources
        apiClass = find ((== "PATHS_TYPE_paths_api") . className) classes
      map classMembers apiClass
        `shouldBe`
          Just
            [ Method
              "users"
              [ Path "api"
              , Path "users"
              , Dyn StringT
              ]
            , Method
              "users_bar"
              [ Path "api"
              , Path "users"
              , Dyn (NonEmptyT StringT)
              , Path "bar"
              ]
            , Method
              "users_baz"
              [ Path "api"
              , Path "users"
              , Dyn StringT
              , Path "baz"
              ]
            , Method
              "users_foo"
              [ Path "api"
              , Path "users"
              , Dyn NumberT
              , Path "foo"
              ]
            ]
  describe "classesToFlow" $ do
    it "should generate formal parameters for each path variable" $ do
      let
        cls =
          [ Class
            "x"
            [ Method
              "y"
              [ Path "x"
              , Dyn StringT
              , Path "y"
              , Dyn NumberT
              , Path "z"
              , Dyn StringT
              ]
            ]
          ]
      normalizeText (classesToFlow cls)
        `shouldBe`
          normalizeText [st|
            class x {
              y(a: string, aa: number, aaa: string): string { return this.root + '/x/' + a + '/y/' + aa.toString() + '/z/' + aaa + ''; }
              root: string;
              constructor(root: string) {
                this.root = root;
              }
            }
          |]
    it "should avoid name shadowing in nested binders" $ do
      let
        cls =
          [ Class
            "x"
            [ Method
              "y"
              [ Path "x"
              , Dyn (NonEmptyT (NonEmptyT NumberT))
              , Path "y"
              ]
            ]
          ]
      normalizeText (classesToFlow cls)
        `shouldBe`
          normalizeText [st|
            class x {
              y(a: Array<Array<number>>): string { return this.root + '/x/' + a.map(function(a1) { return a1.map(function(a2) { return a2.toString() }).join(',') }).join(',') + '/y'; }
              root: string;
              constructor(root: string) {
                this.root = root;
              }
            }
          |]

  describe "genFlowSource" $
    it "should work end-to-end" $ do
      let source = genFlowSource M.empty [] [] "'test'" resources
      normalizeText source
        `shouldBe`
          normalizeText [st|
            /* @flow */
            class PATHS_TYPE_paths {
              api : PATHS_TYPE_paths_api;
              static_pages : PATHS_TYPE_paths_static_pages;
              root: string;
              constructor(root: string) {
                this.root = root;
                this.api = new PATHS_TYPE_paths_api(root);
                this.static_pages = new PATHS_TYPE_paths_static_pages(root);
              }
            }
            class PATHS_TYPE_paths_static_pages {
              root: string;
              constructor(root: string) {
                this.root = root;
              }
            }
            class PATHS_TYPE_paths_api {
              users(a: number): string { return this.root + '/api/users/' + a.toString() + ''; }
              users_bar(a: Array<number>): string { return this.root + '/api/users/' + a.map(function(a1) { return a1.toString() }).join(',') + '/bar'; }
              users_baz(a: string): string { return this.root + '/api/users/' + a + '/baz'; }
              users_foo(a: number): string { return this.root + '/api/users/' + a.toString() + '/foo'; }
              root: string;
              constructor(root: string) {
                this.root = root;
              }
            }
            var PATHS: PATHS_TYPE_paths = new PATHS_TYPE_paths('test');
          |]
