# SPDX-License-Identifier: AGPL-3.0-only
import macros, tables, strutils, xmltree

type
  PrefKind* = enum
    checkbox, select, input

  Pref* = object
    name*: string
    label*: string
    kind*: PrefKind
    # checkbox
    defaultState*: bool
    # select
    defaultOption*: string
    options*: seq[string]
    # input
    defaultInput*: string
    placeholder*: string

  PrefList* = OrderedTable[string, seq[Pref]]

macro genPrefs*(prefDsl: untyped) =
  var table = nnkTableConstr.newTree()
  for category in prefDsl:
    table.add nnkExprColonExpr.newTree(newLit($category[0]))
    table[^1].add nnkPrefix.newTree(newIdentNode("@"), nnkBracket.newTree())
    for pref in category[1]:
      let
        name = newLit($pref[0])
        kind = pref[1]
        label = pref[3][0]
        default = pref[2]
        defaultField =
          case parseEnum[PrefKind]($kind)
          of checkbox: ident("defaultState")
          of select: ident("defaultOption")
          of input: ident("defaultInput")

      var newPref = quote do:
        Pref(kind: `kind`, name: `name`, label: `label`, `defaultField`: `default`)

      for node in pref[3]:
        if node.kind == nnkCall:
          newPref.add nnkExprColonExpr.newTree(node[0], node[1][0])
      table[^1][1][1].add newPref

  let name = ident("prefList")
  result = quote do:
    const `name`*: PrefList = toOrderedTable(`table`)

genPrefs:
  "表示":
    theme(select, "Nitter"):
      "テーマ"

    infiniteScroll(checkbox, false):
      "無限スクロール（JS必須）"

    stickyProfile(checkbox, true):
      "プロフィールサイドバーはページ上に付く"

    bidiSupport(checkbox, false):
      "両方向文字対応（ツイートをクリックしにくくなる）"

    hideTweetStats(checkbox, false):
      "ツイートの詳細（返事、RT、いいね）を隠す"

    hideBanner(checkbox, false):
      "バナーを隠す"

    hidePins(checkbox, false):
      "ピン留めたツイートを隠す"

    hideReplies(checkbox, false):
      "ツイートに返事を隠す"

    squareAvatars(checkbox, false):
      "四角形プロフィールアイコン"

  "メディア":
    mp4Playback(checkbox, true):
      "MP4再生（GIFのみ）"

    hlsPlayback(checkbox, false):
      "HLS動画再生（JS必須）"

    proxyVideos(checkbox, true):
      "動画の再生をサーバーでプロキシーする（遅くなるかも）"

    muteVideos(checkbox, false):
      "デフォルトで動画のミュート"

    autoplayGifs(checkbox, true):
      "自動的にGIFの再生"

  "リンクの変換（無効にするには、かっこにして下さい）":
    replaceTwitter(input, "twitter.owacon.moe"):
      "ツイッター → Nitter"
      placeholder: "twitter.owacon.moe"

    replaceYouTube(input, "youtube.owacon.moe"):
      "ユーチューブ → Invidious"
      placeholder: "youtube.owacon.moe"

    replaceReddit(input, "reddit.owacon.moe"):
      "Reddit → Teddit/Libreddit"
      placeholder: "reddit.owacon.moe"

    replaceInstagram(input, "instagram.owacon.moe"):
      "インスタグラム → Bibliogram"
      placeholder: "instagram.owacon.moe"

    replaceOdysee(input, "odysee.owacon.moe"):
      "オディシー → Librarian"
      placeholder: "odysee.owacon.moe"

    replacePixivA(input, "pixiv.owacon.moe/artworks/?id="):
      "Vixipイラスト"
      placeholder: "pixiv.owacon.moe/artworks/?id="

    replacePixivU(input, "pixiv.owacon.moe/users/?id="):
      "Vixipユーザー"
      placeholder: "pixiv.owacon.moe/users/?id="

    replacePixivB(input, "pixiv.owacon.moe/users/bookmarks/?id="):
      "Vixipユーザーのブックマーク"
      placeholder: "pixiv.owacon.moe/users/bookmarks/?id="

    replacePixivI(input, "pixiv.owacon.moe/users/illusts/?id="):
      "Vixipユーザーのイラスト"
      placeholder: "pixiv.owacon.moe/users/illusts/?id="

    replacePixivM(input, "pixiv.owacon.moe/users/manga/?id="):
      "Vixipユーザーの漫画"
      placeholder: "pixiv.owacon.moe/users/manga/?id="

    replacePixiv(input, "pixiv.owacon.moe"):
      "Pixiv → vixip"
      placeholder: "pixiv.owacon.moe"

iterator allPrefs*(): Pref =
  for k, v in prefList:
    for pref in v:
      yield pref

macro genDefaultPrefs*(): untyped =
  result = nnkStmtList.newTree()
  for pref in allPrefs():
    let
      ident = ident(pref.name)
      name = newLit(pref.name)
      default =
        case pref.kind
        of checkbox: newLit(pref.defaultState)
        of select: newLit(pref.defaultOption)
        of input: newLit(pref.defaultInput)

    result.add quote do:
      defaultPrefs.`ident` = cfg.get("Preferences", `name`, `default`)

macro genCookiePrefs*(cookies): untyped =
  result = nnkStmtList.newTree()
  for pref in allPrefs():
    let
      name = pref.name
      ident = ident(pref.name)
      kind = newLit(pref.kind)
      options = pref.options

    result.add quote do:
      if `name` in `cookies`:
        when `kind` == input or `name` == "theme":
          result.`ident` = `cookies`[`name`]
        elif `kind` == checkbox:
          result.`ident` = `cookies`[`name`] == "on"
        else:
          let value = `cookies`[`name`]
          if value in `options`: result.`ident` = value

macro genCookiePref*(cookies, prefName, res): untyped =
  result = nnkStmtList.newTree()
  for pref in allPrefs():
    let ident = ident(pref.name)
    if ident != prefName:
      continue

    let
      name = pref.name
      kind = newLit(pref.kind)
      options = pref.options

    result.add quote do:
      if `name` in `cookies`:
        when `kind` == input or `name` == "theme":
          `res` = `cookies`[`name`]
        elif `kind` == checkbox:
          `res` = `cookies`[`name`] == "on"
        else:
          let value = `cookies`[`name`]
          if value in `options`: `res` = value

macro genUpdatePrefs*(): untyped =
  result = nnkStmtList.newTree()
  let req = ident("request")
  for pref in allPrefs():
    let
      name = newLit(pref.name)
      kind = newLit(pref.kind)
      options = newLit(pref.options)
      default = nnkDotExpr.newTree(ident("defaultPrefs"), ident(pref.name))

    result.add quote do:
      let val = @`name`
      let isDefault =
        when `kind` == input or `name` == "theme":
          if `default`.len != val.len: false
          else: val == `default`
        elif `kind` == checkbox:
          (val == "on") == `default`
        else:
          val notin `options` or val == `default`

      if isDefault:
        savePref(`name`, "", `req`, expire=true)
      else:
        savePref(`name`, val, `req`)

macro genResetPrefs*(): untyped =
  result = nnkStmtList.newTree()
  let req = ident("request")
  for pref in allPrefs():
    let name = newLit(pref.name)
    result.add quote do:
      savePref(`name`, "", `req`, expire=true)

macro genPrefsType*(): untyped =
  let name = nnkPostfix.newTree(ident("*"), ident("Prefs"))
  result = quote do:
    type `name` = object
      discard

  for pref in allPrefs():
    result[0][2][2].add nnkIdentDefs.newTree(
      nnkPostfix.newTree(ident("*"), ident(pref.name)),
      (case pref.kind
       of checkbox: ident("bool")
       of input, select: ident("string")),
      newEmptyNode())
