module Dsl

open System
open System.Threading.Tasks
open Microsoft.Playwright

[<AbstractClass; Sealed>]
type Locators() =
  static member GetByLabel (text: string) (page: IPage) = page.GetByLabel(text)
  static member GetByRole role options (page: IPage) = page.GetByRole(role, options)
  static member Button options = Locators.GetByRole AriaRole.Button options
  static member Button text = Locators.GetByRole AriaRole.Button (PageGetByRoleOptions(Name = text))
  static member Button(text, ?exact, ?disabled) = Locators.GetByRole AriaRole.Button (PageGetByRoleOptions(Name = text, Exact = defaultArg exact false, Disabled = Option.toNullable disabled))
  static member Heading text = Locators.GetByRole AriaRole.Heading (PageGetByRoleOptions(Name = text))
  static member Heading(text, ?exact, ?level) = Locators.GetByRole AriaRole.Heading (PageGetByRoleOptions(Name = text, Exact = Option.toNullable exact, Level = Option.toNullable level))
  static member Heading options = Locators.GetByRole AriaRole.Heading options
  static member Link text = Locators.GetByRole AriaRole.Link (PageGetByRoleOptions(Name = text))
  static member Link(text, ?exact) = Locators.GetByRole AriaRole.Link (PageGetByRoleOptions(Name = text, Exact = Option.toNullable exact))
  static member Label text = Locators.GetByLabel text
  static member Label(text: string, ?exact) = fun (page: IPage) -> page.GetByLabel(text, PageGetByLabelOptions(Exact = Option.toNullable exact))
  static member Label(pattern: Text.RegularExpressions.Regex) = fun (page: IPage) -> page.GetByLabel(pattern)
  static member Locator (selector: string) (page: IPage) = page.Locator(selector)
  static member ByText(text: string, options) = fun (page: IPage) -> page.GetByText(text, options)
  static member ByText(text: string, ?exact) = fun (page: IPage) -> page.GetByText(text, PageGetByTextOptions(Exact = (defaultArg exact false)))
  static member ByText(text: Text.RegularExpressions.Regex, options) = fun (page: IPage) -> page.GetByText(text, options)
  static member ByText(pattern: Text.RegularExpressions.Regex) = fun (page: IPage) -> page.GetByText(pattern)
  static member ByPlaceholder(text: string) = fun (page: IPage) -> page.GetByPlaceholder(text)
  static member ByPlaceholder(text: string, ?exact) = fun (page: IPage) -> page.GetByPlaceholder(text, PageGetByPlaceholderOptions(Exact = Option.toNullable exact))
  static member ByPlaceholder(pattern: Text.RegularExpressions.Regex) = fun (page: IPage) -> page.GetByPlaceholder(pattern)
  static member ByTestId(text: string) = fun (page: IPage) -> page.GetByTestId(text)
  static member ByTestId(pattern: Text.RegularExpressions.Regex) = fun (page: IPage) -> page.GetByTestId(pattern)
  static member ByAltText(text: string) = fun (page: IPage) -> page.GetByAltText(text)
  static member ByAltText(text: string, ?exact) = fun (page: IPage) -> page.GetByAltText(text, PageGetByAltTextOptions(Exact = Option.toNullable exact))
  static member ByAltText(pattern: Text.RegularExpressions.Regex) = fun (page: IPage) -> page.GetByAltText(pattern)
  static member ByTitle(text: string) = fun (page: IPage) -> page.GetByTitle(text)
  static member ByTitle(text: string, ?exact) = fun (page: IPage) -> page.GetByTitle(text, PageGetByTitleOptions(Exact = Option.toNullable exact))
  static member ByTitle(pattern: Text.RegularExpressions.Regex) = fun (page: IPage) -> page.GetByTitle(pattern)
  static member Textbox text = Locators.GetByRole AriaRole.Textbox (PageGetByRoleOptions(Name = text))
  static member Textbox(text, ?exact) = Locators.GetByRole AriaRole.Textbox (PageGetByRoleOptions(Name = text, Exact = Option.toNullable exact))
  static member Checkbox text = Locators.GetByRole AriaRole.Checkbox (PageGetByRoleOptions(Name = text))
  static member Checkbox(text, ?exact, ?isChecked) = Locators.GetByRole AriaRole.Checkbox (PageGetByRoleOptions(Name = text, Exact = Option.toNullable exact, Checked = Option.toNullable isChecked))
  static member Combobox text = Locators.GetByRole AriaRole.Combobox (PageGetByRoleOptions(Name = text))
  static member Combobox(text, ?expanded) = Locators.GetByRole AriaRole.Combobox (PageGetByRoleOptions(Name = text, Expanded = Option.toNullable expanded))
  static member Tab text = Locators.GetByRole AriaRole.Tab (PageGetByRoleOptions(Name = text))
  static member Tab(text, ?selected) = Locators.GetByRole AriaRole.Tab (PageGetByRoleOptions(Name = text, Selected = Option.toNullable selected))
  static member MenuItem text = Locators.GetByRole AriaRole.Menuitem (PageGetByRoleOptions(Name = text))

let expect f (locator: ILocator) = f (Assertions.Expect(locator))

type Locator = IPage -> ILocator
type Transition<'t> = Locator * (unit -> 't)

let inline (||>) (parent: Locator) f page = f (parent page)

type State<'pom> = Task<'pom>

type UiTestBuilder<'initialPlr>(page: IPage, plr: 'initialPlr) =

  let withPage (state: State<'pom>) f =
    task {
      let! pom = state
      let! _ = f page
      return pom
    }

  let withPageTask (state: State<'pom>) (f: IPage -> Task) =
    task {
      let! pom = state
      do! f page
      return pom
    }

  let act (state: State<'pom>) (locator: 'pom -> Locator)  (f: ILocator -> Task) =
    task {
      let! pom = state
      let locator = locator pom page
      do! f locator
      return pom
    }

  let applyWithoutTransition (state: State<'pom>) (locator: 'pom -> Transition<_>)  (f: ILocator -> Task) =
    task {
      let! pom = state
      let locator, _ = locator pom
      do! f (locator page)
      return pom
    }

  let apply (state: State<'pom>) (locator: 'pom -> Transition<_>)  (f: ILocator -> Task) =
    task {
      let! pom = state
      let locator, next = locator pom
      do! f (locator page)
      return next ()
    }

  member _.Yield(()) : State<'initialPlr> =
    Task.FromResult plr

  [<CustomOperation("fill", MaintainsVariableSpace  = true)>]
  member _.Fill(state: State<'page>, locator, text: string, ?timeout: float32, ?force: bool) =
    act state locator _.FillAsync(text, LocatorFillOptions(Timeout = Option.toNullable timeout, Force = Option.toNullable force))

  [<CustomOperation("write", MaintainsVariableSpace = true)>]
  member _.Write(state, locator, text: string, ?delay: float32) =
    act state locator _.PressSequentiallyAsync(text, LocatorPressSequentiallyOptions(Delay = Option.toNullable delay))

  [<CustomOperation("page", MaintainsVariableSpace = true)>]
  member _.Page(state, f) = withPage state f

  [<CustomOperation("page", MaintainsVariableSpace = true)>]
  member _.Page(state, f) = withPageTask state f

  [<CustomOperation("expectVisible", MaintainsVariableSpace = true)>]
  member _.ExpectVisible(state, getLocator, ?timeout: float32) =
    act state getLocator (expect _.ToBeVisibleAsync(LocatorAssertionsToBeVisibleOptions(Timeout = Option.toNullable timeout)))

  [<CustomOperation("expectVisible", MaintainsVariableSpace = true)>]
  member _.ExpectVisible(state, getLocator, ?timeout: float32) =
    applyWithoutTransition state getLocator (expect _.ToBeVisibleAsync(LocatorAssertionsToBeVisibleOptions(Timeout = Option.toNullable timeout)))

  [<CustomOperation("expectVisible", MaintainsVariableSpace = true)>]
  member _.ExpectVisible(state, getLocator, arg) =
    act
      state
      (fun plr -> getLocator plr arg)
      (expect _.ToBeVisibleAsync())

  [<CustomOperation("click")>]
  member _.Click(state, locator) = act state locator _.ClickAsync()

  [<CustomOperation("click")>]
  member _.Click(state, locator, options: LocatorClickOptions) = act state locator _.ClickAsync(options)

  [<CustomOperation("click")>]
  member _.Click(state, getTransition) = apply state getTransition _.ClickAsync()

  [<CustomOperation("click")>]
  member _.Click(state, getTransition, options: LocatorClickOptions) = apply state getTransition _.ClickAsync(options)

  [<CustomOperation("click")>]
  member _.Click(state, getTransition, arg, options: LocatorClickOptions) =
    act
      state
      (fun plr -> getTransition plr arg )
      _.ClickAsync(options)

  [<CustomOperation("click")>]
  member _.Click(state, getTransition, arg) =
    apply
      state
      (fun plr -> getTransition plr arg )
      _.ClickAsync()

  [<CustomOperation("printf")>]
  member _.PrintF(state, msg, arg) =
    task {
      let! state = state
      printfn msg arg
      return state
    }

  [<CustomOperation("print")>]
  member _.Print(state, msg) =
    task {
      let! state = state
      printfn "%s" msg
      return state
    }

  [<CustomOperation("wait_s")>]
  member _.WaitS(state, delay: int) =
    task {
      let! state = state
      do! Task.Delay(1000 * delay)
      return state
    }
  [<CustomOperation("wait_ms")>]
  member _.WaitMs(state, delay: int) =
    task {
      let! state = state
      do! Task.Delay(delay)
      return state
    }

  [<CustomOperation("takeAriaSnapshot")>]
  member _.TakeAriaSnapshot state =
    task {
      let! state = state
      let! snapshot = page.AriaSnapshotAsync()
      printfn "%s" snapshot
      return state
    }

  [<CustomOperation("takeAriaSnapshot")>]
  member _.TakeAriaSnapshot(state, locator) =
    act
      state
      locator
      (fun x ->
        task {
          let! snapshot = x.AriaSnapshotAsync()
          printfn "%s" snapshot
        }
      )

  [<CustomOperation("takeAriaSnapshot")>]
  member _.TakeAriaSnapshot(state, locator) =
    applyWithoutTransition
      state
      locator
      (fun x ->
        task {
          let! snapshot = x.AriaSnapshotAsync()
          printfn "%s" snapshot
          return ()
        }
      )

  [<CustomOperation("hover", MaintainsVariableSpace = true)>]
  member _.Hover(state, locator, ?force: bool) = act state locator _.HoverAsync(LocatorHoverOptions(Force = Option.toNullable force))

  [<CustomOperation("hover", MaintainsVariableSpace = true)>]
  member _.Hover(state, getTransition, ?force: bool) = apply state getTransition _.HoverAsync(LocatorHoverOptions(Force = Option.toNullable force))

  [<CustomOperation("dblClick")>]
  member _.DblClick(state, locator) = act state locator _.DblClickAsync()

  [<CustomOperation("dblClick")>]
  member _.DblClick(state, getTransition) = apply state getTransition _.DblClickAsync()

  [<CustomOperation("check", MaintainsVariableSpace = true)>]
  member _.Check(state, locator) = act state locator _.CheckAsync()

  [<CustomOperation("uncheck", MaintainsVariableSpace = true)>]
  member _.Uncheck(state, locator) = act state locator _.UncheckAsync()

  [<CustomOperation("press", MaintainsVariableSpace = true)>]
  member _.Press(state, locator, key: string) = act state locator _.PressAsync(key)

  [<CustomOperation("selectOption", MaintainsVariableSpace = true)>]
  member _.SelectOption(state, locator, value: string) = act state locator _.SelectOptionAsync(value)

  [<CustomOperation("focus", MaintainsVariableSpace = true)>]
  member _.Focus(state, locator) = act state locator _.FocusAsync()

  [<CustomOperation("clear", MaintainsVariableSpace = true)>]
  member _.Clear(state, locator) = act state locator _.ClearAsync()

  [<CustomOperation("waitFor", MaintainsVariableSpace = true)>]
  member _.WaitFor(state, locator, ?timeout: float32) =
    act state locator _.WaitForAsync(LocatorWaitForOptions(Timeout = Option.toNullable timeout))

  [<CustomOperation("waitFor", MaintainsVariableSpace = true)>]
  member _.WaitFor(state, locator, waitState: WaitForSelectorState) =
    act state locator _.WaitForAsync(LocatorWaitForOptions(State = waitState))

  [<CustomOperation("waitFor", MaintainsVariableSpace = true)>]
  member _.WaitFor(state, getTransition, ?timeout: float32) =
    applyWithoutTransition state getTransition  _.WaitForAsync(LocatorWaitForOptions(Timeout = Option.toNullable timeout))

  [<CustomOperation("expectHidden", MaintainsVariableSpace = true)>]
  member _.ExpectHidden(state, getLocator, ?timeout: float32) =
    act state getLocator (expect _.ToBeHiddenAsync(LocatorAssertionsToBeHiddenOptions(Timeout = Option.toNullable timeout)))

  [<CustomOperation("expectHidden", MaintainsVariableSpace = true)>]
  member _.ExpectHidden(state, getLocator, ?timeout: float32) =
    applyWithoutTransition state getLocator  (expect _.ToBeHiddenAsync(LocatorAssertionsToBeHiddenOptions(Timeout = Option.toNullable timeout)))

  [<CustomOperation("expectEnabled", MaintainsVariableSpace = true)>]
  member _.ExpectEnabled(state, getLocator, ?timeout: float32) =
    act state getLocator (expect _.ToBeEnabledAsync(LocatorAssertionsToBeEnabledOptions(Timeout = Option.toNullable timeout)))

  [<CustomOperation("expectEnabled", MaintainsVariableSpace = true)>]
  member _.ExpectEnabled(state, getLocator, ?timeout: float32) =
    applyWithoutTransition state getLocator  (expect _.ToBeEnabledAsync(LocatorAssertionsToBeEnabledOptions(Timeout = Option.toNullable timeout)))

  [<CustomOperation("expectDisabled", MaintainsVariableSpace = true)>]
  member _.ExpectDisabled(state, getLocator, ?timeout: float32) =
    act state getLocator (expect _.ToBeDisabledAsync(LocatorAssertionsToBeDisabledOptions(Timeout = Option.toNullable timeout)))

  [<CustomOperation("expectDisabled", MaintainsVariableSpace = true)>]
  member _.ExpectDisabled(state, getLocator, ?timeout: float32) =
    applyWithoutTransition state getLocator  (expect _.ToBeDisabledAsync(LocatorAssertionsToBeDisabledOptions(Timeout = Option.toNullable timeout)))

  [<CustomOperation("expectChecked", MaintainsVariableSpace = true)>]
  member _.ExpectChecked(state, getLocator, ?timeout: float32) =
    act state getLocator (expect _.ToBeCheckedAsync(LocatorAssertionsToBeCheckedOptions(Timeout = Option.toNullable timeout)))

  [<CustomOperation("expectChecked", MaintainsVariableSpace = true)>]
  member _.ExpectChecked(state, getLocator, ?timeout: float32) =
    applyWithoutTransition state getLocator  (expect _.ToBeCheckedAsync(LocatorAssertionsToBeCheckedOptions(Timeout = Option.toNullable timeout)))

  [<CustomOperation("expectText", MaintainsVariableSpace = true)>]
  member _.ExpectText(state, getLocator, text: string, ?ignoreCase: bool, ?timeout: float32) =
    act state getLocator (expect _.ToHaveTextAsync(text, LocatorAssertionsToHaveTextOptions(IgnoreCase = Option.toNullable ignoreCase, Timeout = Option.toNullable timeout)))

  [<CustomOperation("expectText", MaintainsVariableSpace = true)>]
  member _.ExpectText(state, getLocator, text: string, ?ignoreCase: bool, ?timeout: float32) =
    applyWithoutTransition state getLocator  (expect _.ToHaveTextAsync(text, LocatorAssertionsToHaveTextOptions(IgnoreCase = Option.toNullable ignoreCase, Timeout = Option.toNullable timeout)))

  [<CustomOperation("expectContainsText", MaintainsVariableSpace = true)>]
  member _.ExpectContainsText(state, getLocator, text: string, ?ignoreCase: bool, ?timeout: float32) =
    act state getLocator (expect _.ToContainTextAsync(text, LocatorAssertionsToContainTextOptions(IgnoreCase = Option.toNullable ignoreCase, Timeout = Option.toNullable timeout)))

  [<CustomOperation("expectContainsText", MaintainsVariableSpace = true)>]
  member _.ExpectContainsText(state, getLocator, text: string, ?ignoreCase: bool, ?timeout: float32) =
    applyWithoutTransition state getLocator  (expect _.ToContainTextAsync(text, LocatorAssertionsToContainTextOptions(IgnoreCase = Option.toNullable ignoreCase, Timeout = Option.toNullable timeout)))

  [<CustomOperation("expectCount", MaintainsVariableSpace = true)>]
  member _.ExpectCount(state, getLocator, count: int, ?timeout: float32) =
    act state getLocator (expect _.ToHaveCountAsync(count, LocatorAssertionsToHaveCountOptions(Timeout = Option.toNullable timeout)))

  [<CustomOperation("reload", MaintainsVariableSpace = true)>]
  member _.Reload(state) = withPage state _.ReloadAsync()

  [<CustomOperation("goBack", MaintainsVariableSpace = true)>]
  member _.GoBack(state) = withPage state _.GoBackAsync()

  [<CustomOperation("goForward", MaintainsVariableSpace = true)>]
  member _.GoForward(state) = withPage state _.GoForwardAsync()

  [<CustomOperation("waitForURL", MaintainsVariableSpace = true)>]
  member _.WaitForURL(state, url: string) = withPageTask state _.WaitForURLAsync(url)

  [<CustomOperation("screenshot", MaintainsVariableSpace = true)>]
  member _.Screenshot(state, path: string) =
    withPage state _.ScreenshotAsync(PageScreenshotOptions(Path = path))

  [<CustomOperation("withPage")>]
  member _.WithPagh(state,f) =
    withPage state f

let pageTest page = UiTestBuilder page