# SPDX-License-Identifier: AGPL-3.0-only
import times, sequtils, options, tables
import prefs_impl

genPrefsType()

type
  RateLimitError* = object of CatchableError
  NoSessionsError* = object of CatchableError
  InternalError* = object of CatchableError
  BadClientError* = object of CatchableError

  TimelineKind* {.pure.} = enum
    tweets, replies, media, articles

  ApiUrl* = object
    endpoint*: string
    params*: seq[(string, string)]
    skipTid*: bool

  ApiReq* = object
    oauth*: ApiUrl
    cookie*: ApiUrl

  RateLimit* = object
    limit*: int
    remaining*: int
    reset*: int

  SessionKind* = enum
    oauth
    cookie

  Session* = ref object
    id*: int64
    username*: string
    pending*: int
    limited*: bool
    limitedAt*: int
    apis*: Table[string, RateLimit]
    case kind*: SessionKind
    of oauth:
      oauthToken*: string
      oauthSecret*: string
    of cookie:
      authToken*: string
      ct0*: string

  Error* = enum
    null = 0
    noUserMatches = 17
    protectedUser = 22
    missingParams = 25
    timeout = 29
    couldntAuth = 32
    doesntExist = 34
    unauthorized = 37
    invalidParam = 47
    userNotFound = 50
    suspended = 63
    rateLimited = 88
    expiredToken = 89
    listIdOrSlug = 112
    timelineUnavailable = 131
    tweetNotFound = 144
    tweetNotAuthorized = 179
    forbidden = 200
    badRequest = 214
    badToken = 239
    locked = 326
    noCsrf = 353
    tweetUnavailable = 421
    tweetCensored = 422

  VerifiedType* = enum
    none = "None"
    blue = "Blue"
    business = "Business"
    government = "Government"

  User* = object
    id*: string
    username*: string
    fullname*: string
    location*: string
    website*: string
    bio*: string
    userPic*: string
    banner*: string
    pinnedTweet*: int64
    following*: int
    followers*: int
    tweets*: int
    likes*: int
    media*: int
    verifiedType*: VerifiedType
    protected*: bool
    suspended*: bool
    joinDate*: DateTime

  AccountInfo* = object
    username*: string
    fullname*: string
    userPic*: string
    joinDate*: DateTime
    verifiedType*: VerifiedType
    suspended*: bool
    basedIn*: string
    source*: string
    usernameChanges*: int
    lastUsernameChange*: DateTime
    affiliateUsername*: string
    affiliateLabel*: string
    isIdentityVerified*: bool
    verifiedSince*: DateTime
    overrideVerifiedYear*: int

  Broadcast* = object
    id*: string
    title*: string
    state*: string
    thumb*: string
    mediaKey*: string
    m3u8Url*: string
    totalWatched*: int
    startTime*: DateTime
    endTime*: DateTime
    replayStart*: int
    availableForReplay*: bool
    user*: User

  SpaceParticipant* = object
    userId*: string
    username*: string
    displayName*: string
    avatarUrl*: string
    isVerified*: bool

  AudioSpace* = object
    id*: string
    title*: string
    state*: string
    mediaKey*: string
    m3u8Url*: string
    totalLiveListeners*: int
    totalReplayWatched*: int
    startTime*: DateTime
    endTime*: DateTime
    availableForReplay*: bool
    creator*: User
    admins*: seq[SpaceParticipant]
    speakers*: seq[SpaceParticipant]

  VideoType* = enum
    m3u8 = "application/x-mpegURL"
    mp4 = "video/mp4"
    vmap = "video/vmap"

  VideoVariant* = object
    contentType*: VideoType
    url*: string
    bitrate*: int
    resolution*: int

  Video* = object
    durationMs*: int
    url*: string
    thumb*: string
    available*: bool
    reason*: string
    title*: string
    description*: string
    playbackType*: VideoType
    variants*: seq[VideoVariant]

  QueryKind* = enum
    posts, replies, media, users, tweets, userList, followers, following, lists, top,
    articles

  RankingMode* = enum
    Relevance, Recency, Likes

  Query* = object
    kind*: QueryKind
    view*: string
    text*: string
    filters*: seq[string]
    includes*: seq[string]
    excludes*: seq[string]
    fromUser*: seq[string]
    since*: string
    until*: string
    minLikes*: string
    sep*: string

  Gif* = object
    url*: string
    thumb*: string
    altText*: string

  Photo* = object
    url*: string
    altText*: string

  MediaKind* = enum
    photoMedia
    videoMedia
    gifMedia

  Media* = object
    case kind*: MediaKind
    of photoMedia:
      photo*: Photo
    of videoMedia:
      video*: Video
    of gifMedia:
      gif*: Gif

  MediaEntities* = seq[Media]

  GalleryPhoto* = object
    url*: string
    tweetId*: string
    color*: string

  PhotoRail* = seq[GalleryPhoto]

  Article* = ref object
    title*: string
    coverImage*: string
    user*: User
    time*: DateTime
    stats*: TweetStats
    paragraphs*: seq[ArticleParagraph]
    entities*: Table[int, ArticleEntity]
    media*: Table[string, ArticleMedia]

  ArticleParagraph* = object
    text*: string
    kind*: string
    inlineStyles*: seq[ArticleStyle]
    entityRanges*: seq[ArticleEntityRange]

  ArticleStyle* = object
    offset*: int
    length*: int
    style*: string

  ArticleEntityRange* = object
    offset*: int
    length*: int
    key*: int

  ArticleEntity* = object
    kind*: string
    url*: string
    mediaIds*: seq[string]
    tweetId*: string
    markdown*: string
    caption*: string

  ArticleMedia* = object
    kind*: string
    url*: string

  Poll* = object
    options*: seq[string]
    values*: seq[int]
    votes*: int
    leader*: int
    status*: string

  CardKind* = enum
    amplify = "amplify"
    app = "app"
    appPlayer = "appplayer"
    player = "player"
    summary = "summary"
    summaryLarge = "summary_large_image"
    promoWebsite = "promo_website"
    promoVideo = "promo_video_website"
    promoVideoConvo = "promo_video_convo"
    promoImageConvo = "promo_image_convo"
    promoImageApp = "promo_image_app"
    storeLink = "direct_store_link_app"
    liveEvent = "live_event"
    broadcast = "broadcast"
    periscope = "periscope_broadcast"
    unified = "unified_card"
    moment = "moment"
    messageMe = "message_me"
    videoDirectMessage = "video_direct_message"
    imageDirectMessage = "image_direct_message"
    audiospace = "audiospace"
    newsletterPublication = "newsletter_publication"
    jobDetails = "job_details"
    hidden
    unknown

  Card* = object
    kind*: CardKind
    url*: string
    title*: string
    dest*: string
    text*: string
    image*: string
    video*: Option[Video]

  TweetStats* = object
    replies*: int
    retweets*: int
    likes*: int
    views*: int

  ArticlePreview* = object
    title*: string
    previewText*: string
    coverImage*: string
    tweetId*: int64

  Tweet* = ref object
    id*: int64
    threadId*: int64
    replyId*: int64
    user*: User
    text*: string
    time*: DateTime
    reply*: seq[string]
    pinned*: bool
    hasThread*: bool
    available*: bool
    tombstone*: string
    location*: string
    # Unused, needed for backwards compat
    source*: string
    stats*: TweetStats
    retweet*: Option[Tweet]
    attribution*: Option[User]
    attributionLink*: string
    mediaTags*: seq[User]
    quote*: Option[Tweet]
    card*: Option[Card]
    poll*: Option[Poll]
    media*: MediaEntities
    history*: seq[int64]
    note*: string
    isAd*: bool
    isAI*: bool
    articlePreview*: Option[ArticlePreview]

  Tweets* = seq[Tweet]

  Result*[T] = object
    content*: seq[T]
    top*, bottom*: string
    beginning*: bool
    query*: Query

  Chain* = object
    content*: Tweets
    hasMore*: bool
    cursor*: string
    related*: bool

  Conversation* = ref object
    tweet*: Tweet
    before*: Chain
    after*: Chain
    replies*: Result[Chain]

  EditHistory* = object
    latest*: Tweet
    history*: Tweets

  Timeline* = Result[Tweets]

  Profile* = object
    user*: User
    photoRail*: PhotoRail
    pinned*: Option[Tweet]
    tweets*: Timeline
    accountInfo*: AccountInfo

  List* = object
    id*: string
    name*: string
    userId*: string
    username*: string
    description*: string
    members*: int
    banner*: string

  ListSearchResult* = object
    list*: List
    owner*: User
    followersContext*: string
    facepiles*: seq[string]

  CommunityRule* = object
    name*: string
    description*: string

  Community* = object
    id*: string
    name*: string
    description*: string
    memberCount*: int
    banner*: string
    creator*: User
    category*: string
    joinPolicy*: string
    createdAt*: DateTime
    rules*: seq[CommunityRule]
    hashtags*: seq[string]

  GlobalObjects* = ref object
    tweets*: Table[string, Tweet]
    users*: Table[string, User]

  Config* = ref object
    address*: string
    port*: int
    useHttps*: bool
    httpMaxConns*: int
    title*: string
    hostname*: string
    staticDir*: string

    hmacKey*: string
    base64Media*: bool
    minTokens*: int
    enableRSSUserTweets*: bool
    enableRSSUserReplies*: bool
    enableRSSUserMedia*: bool
    enableRSSUserArticles*: bool
    enableRSSSearch*: bool
    enableRSSList*: bool
    enableDebug*: bool
    proxy*: string
    proxyAuth*: string
    apiProxy*: string
    disableTid*: bool
    maxConcurrentReqs*: int
    maxRetries*: int
    retryDelayMs*: int

    rssCacheTime*: int
    listCacheTime*: int

    redisHost*: string
    redisPort*: int
    redisConns*: int
    redisMaxConns*: int
    redisPassword*: string

  Rss* = object
    feed*, cursor*: string

proc contains*(thread: Chain; tweet: Tweet): bool =
  thread.content.anyIt(it.id == tweet.id)

proc add*(timeline: var seq[Tweets]; tweet: Tweet) =
  timeline.add @[tweet]

proc getPhotos*(tweet: Tweet): seq[Photo] =
  tweet.media.filterIt(it.kind == photoMedia).mapIt(it.photo)

proc getVideos*(tweet: Tweet): seq[Video] =
  tweet.media.filterIt(it.kind == videoMedia).mapIt(it.video)

proc hasPhotos*(tweet: Tweet): bool =
  tweet.media.anyIt(it.kind == photoMedia)

proc hasVideos*(tweet: Tweet): bool =
  tweet.media.anyIt(it.kind == videoMedia)

proc hasGifs*(tweet: Tweet): bool =
  tweet.media.anyIt(it.kind == gifMedia)

proc getThumb*(media: Media): string =
  case media.kind
  of photoMedia: media.photo.url
  of videoMedia: media.video.thumb
  of gifMedia: media.gif.thumb