# Pleroma: A lightweight social networking server
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.ApiSpec.StatusOperation do
  alias OpenApiSpex.Operation
  alias OpenApiSpex.Schema
  alias Pleroma.Web.ApiSpec.AccountOperation
  alias Pleroma.Web.ApiSpec.Schemas.ApiError
  alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
  alias Pleroma.Web.ApiSpec.Schemas.FlakeID
  alias Pleroma.Web.ApiSpec.Schemas.ScheduledStatus
  alias Pleroma.Web.ApiSpec.Schemas.Status
  alias Pleroma.Web.ApiSpec.Schemas.VisibilityScope

  import Pleroma.Web.ApiSpec.Helpers

  def open_api_operation(action) do
    operation = String.to_existing_atom("#{action}_operation")
    apply(__MODULE__, operation, [])
  end

  def index_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Get multiple statuses by IDs",
      security: [%{"oAuth" => ["read:statuses"]}],
      parameters: [
        Operation.parameter(
          :ids,
          :query,
          %Schema{type: :array, items: FlakeID},
          "Array of status IDs"
        )
      ],
      operationId: "StatusController.index",
      responses: %{
        200 => Operation.response("Array of Status", "application/json", array_of_statuses())
      }
    }
  end

  def create_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Publish new status",
      security: [%{"oAuth" => ["write:statuses"]}],
      description: "Post a new status",
      operationId: "StatusController.create",
      requestBody: request_body("Parameters", create_request(), required: true),
      responses: %{
        200 =>
          Operation.response(
            "Status. When `scheduled_at` is present, ScheduledStatus is returned instead",
            "application/json",
            %Schema{oneOf: [Status, ScheduledStatus]}
          ),
        422 => Operation.response("Bad Request", "application/json", ApiError)
      }
    }
  end

  def show_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "View specific status",
      description: "View information about a status",
      operationId: "StatusController.show",
      security: [%{"oAuth" => ["read:statuses"]}],
      parameters: [id_param()],
      responses: %{
        200 => status_response(),
        404 => Operation.response("Not Found", "application/json", ApiError)
      }
    }
  end

  def delete_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Delete status",
      security: [%{"oAuth" => ["write:statuses"]}],
      description: "Delete one of your own statuses",
      operationId: "StatusController.delete",
      parameters: [id_param()],
      responses: %{
        200 => empty_object_response(),
        403 => Operation.response("Forbidden", "application/json", ApiError),
        404 => Operation.response("Not Found", "application/json", ApiError)
      }
    }
  end

  def reblog_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Boost",
      security: [%{"oAuth" => ["write:statuses"]}],
      description: "Share a status",
      operationId: "StatusController.reblog",
      parameters: [id_param()],
      requestBody:
        request_body("Parameters", %Schema{
          type: :object,
          properties: %{
            visibility: %Schema{allOf: [VisibilityScope], default: "public"}
          }
        }),
      responses: %{
        200 => status_response(),
        404 => Operation.response("Not Found", "application/json", ApiError)
      }
    }
  end

  def unreblog_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Undo boost",
      security: [%{"oAuth" => ["write:statuses"]}],
      description: "Undo a reshare of a status",
      operationId: "StatusController.unreblog",
      parameters: [id_param()],
      responses: %{
        200 => status_response(),
        404 => Operation.response("Not Found", "application/json", ApiError)
      }
    }
  end

  def favourite_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Favourite",
      security: [%{"oAuth" => ["write:favourites"]}],
      description: "Add a status to your favourites list",
      operationId: "StatusController.favourite",
      parameters: [id_param()],
      responses: %{
        200 => status_response(),
        404 => Operation.response("Not Found", "application/json", ApiError)
      }
    }
  end

  def unfavourite_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Undo favourite",
      security: [%{"oAuth" => ["write:favourites"]}],
      description: "Remove a status from your favourites list",
      operationId: "StatusController.unfavourite",
      parameters: [id_param()],
      responses: %{
        200 => status_response(),
        404 => Operation.response("Not Found", "application/json", ApiError)
      }
    }
  end

  def pin_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Pin to profile",
      security: [%{"oAuth" => ["write:accounts"]}],
      description: "Feature one of your own public statuses at the top of your profile",
      operationId: "StatusController.pin",
      parameters: [id_param()],
      responses: %{
        200 => status_response(),
        400 => Operation.response("Error", "application/json", ApiError)
      }
    }
  end

  def unpin_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Unpin to profile",
      security: [%{"oAuth" => ["write:accounts"]}],
      description: "Unfeature a status from the top of your profile",
      operationId: "StatusController.unpin",
      parameters: [id_param()],
      responses: %{
        200 => status_response(),
        400 => Operation.response("Error", "application/json", ApiError)
      }
    }
  end

  def bookmark_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Bookmark",
      security: [%{"oAuth" => ["write:bookmarks"]}],
      description: "Privately bookmark a status",
      operationId: "StatusController.bookmark",
      parameters: [id_param()],
      responses: %{
        200 => status_response()
      }
    }
  end

  def unbookmark_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Undo bookmark",
      security: [%{"oAuth" => ["write:bookmarks"]}],
      description: "Remove a status from your private bookmarks",
      operationId: "StatusController.unbookmark",
      parameters: [id_param()],
      responses: %{
        200 => status_response()
      }
    }
  end

  def mute_conversation_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Mute conversation",
      security: [%{"oAuth" => ["write:mutes"]}],
      description:
        "Do not receive notifications for the thread that this status is part of.",
      operationId: "StatusController.mute_conversation",
      parameters: [id_param()],
      responses: %{
        200 => status_response(),
        400 => Operation.response("Error", "application/json", ApiError)
      }
    }
  end

  def unmute_conversation_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Unmute conversation",
      security: [%{"oAuth" => ["write:mutes"]}],
      description:
        "Start receiving notifications again for the thread that this status is part of",
      operationId: "StatusController.unmute_conversation",
      parameters: [id_param()],
      responses: %{
        200 => status_response(),
        400 => Operation.response("Error", "application/json", ApiError)
      }
    }
  end

  def card_operation do
    %Operation{
      tags: ["Statuses"],
      deprecated: true,
      summary: "Preview card",
      description: "Deprecated in favor of card property inlined on Status entity",
      operationId: "StatusController.card",
      parameters: [id_param()],
      security: [%{"oAuth" => ["read:statuses"]}],
      responses: %{
        200 =>
          Operation.response("Card", "application/json", %Schema{
            type: :object,
            nullable: true,
            properties: %{
              type: %Schema{type: :string, enum: ["link", "photo", "video", "rich"]},
              provider_name: %Schema{type: :string, nullable: true},
              provider_url: %Schema{type: :string, format: :uri},
              url: %Schema{type: :string, format: :uri},
              image: %Schema{type: :string, nullable: true, format: :uri},
              title: %Schema{type: :string},
              description: %Schema{type: :string}
            }
          })
      }
    }
  end

  def favourited_by_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Favourited by",
      description: "View who favourited a given status",
      operationId: "StatusController.favourited_by",
      security: [%{"oAuth" => ["read:accounts"]}],
      parameters: [id_param()],
      responses: %{
        200 =>
          Operation.response(
            "Array of Accounts",
            "application/json",
            AccountOperation.array_of_accounts()
          ),
        404 => Operation.response("Not Found", "application/json", ApiError)
      }
    }
  end

  def reblogged_by_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Boosted by",
      description: "View who boosted a given status",
      operationId: "StatusController.reblogged_by",
      security: [%{"oAuth" => ["read:accounts"]}],
      parameters: [id_param()],
      responses: %{
        200 =>
          Operation.response(
            "Array of Accounts",
            "application/json",
            AccountOperation.array_of_accounts()
          ),
        404 => Operation.response("Not Found", "application/json", ApiError)
      }
    }
  end

  def context_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Parent and child statuses",
      description: "View statuses above and below this status in the thread",
      operationId: "StatusController.context",
      security: [%{"oAuth" => ["read:statuses"]}],
      parameters: [id_param()],
      responses: %{
        200 => Operation.response("Context", "application/json", context())
      }
    }
  end

  def favourites_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Favourited statuses",
      description: "Statuses the user has favourited",
      operationId: "StatusController.favourites",
      parameters: pagination_params(),
      security: [%{"oAuth" => ["read:favourites"]}],
      responses: %{
        200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
      }
    }
  end

  def bookmarks_operation do
    %Operation{
      tags: ["Statuses"],
      summary: "Bookmarked statuses",
      description: "Statuses the user has bookmarked",
      operationId: "StatusController.bookmarks",
      parameters: [
        Operation.parameter(:with_relationships, :query, BooleanLike, "Include relationships")
        | pagination_params()
      ],
      security: [%{"oAuth" => ["read:bookmarks"]}],
      responses: %{
        200 => Operation.response("Array of Statuses", "application/json", array_of_statuses())
      }
    }
  end

  defp array_of_statuses do
    %Schema{type: :array, items: Status, example: [Status.schema().example]}
  end

  defp create_request do
    %Schema{
      title: "StatusCreateRequest",
      type: :object,
      properties: %{
        status: %Schema{
          type: :string,
          description:
            "Text content of the status. If `media_ids` is provided, this becomes optional. Attaching a `poll` is optional while `status` is provided."
        },
        media_ids: %Schema{
          type: :array,
          items: %Schema{type: :string},
          description:
            "Array of Attachment ids to be attached as media."
        },
        poll: %Schema{
          type: :object,
          required: [:options],
          properties: %{
            options: %Schema{
              type: :array,
              items: %Schema{type: :string},
              description:
                "Array of possible answers. Must be provided with `poll[expires_in]`."
            },
            expires_in: %Schema{
              type: :integer,
              description:
                "Duration the poll should be open, in seconds. Must be provided with `poll[options]`"
            },
            multiple: %Schema{type: :boolean, description: "Allow multiple choices?"},
            hide_totals: %Schema{
              type: :boolean,
              description: "Hide vote counts until the poll ends?"
            }
          }
        },
        in_reply_to_id: %Schema{
          allOf: [FlakeID],
          description: "ID of the status being replied to, if status is a reply"
        },
        sensitive: %Schema{
          type: :boolean,
          description: "Mark status and attached media as sensitive?"
        },
        spoiler_text: %Schema{
          type: :string,
          description:
            "Text to be shown as a warning or subject before the actual content. Statuses are generally collapsed behind this field."
        },
        scheduled_at: %Schema{
          type: :string,
          format: :"date-time",
          nullable: true,
          description:
            "ISO 8601 Datetime at which to schedule a status. Providing this paramter will cause ScheduledStatus to be returned instead of Status. Must be at least 5 minutes in the future."
        },
        language: %Schema{type: :string, description: "ISO 639 language code for this status."},
        # Pleroma-specific properties:
        preview: %Schema{
          type: :boolean,
          description:
            "If set to `true` the post won't be actually posted, but the status entitiy would still be rendered back. This could be useful for previewing rich text/custom emoji, for example"
        },
        content_type: %Schema{
          type: :string,
          description:
            "The MIME type of the status, it is transformed into HTML by the backend. You can get the list of the supported MIME types with the nodeinfo endpoint."
        },
        to: %Schema{
          type: :array,
          items: %Schema{type: :string},
          description:
            "A list of nicknames (like `lain@soykaf.club` or `lain` on the local server) that will be used to determine who is going to be addressed by this post. Using this will disable the implicit addressing by mentioned names in the `status` body, only the people in the `to` list will be addressed. The normal rules for for post visibility are not affected by this and will still apply"
        },
        visibility: %Schema{
          anyOf: [
            VisibilityScope,
            %Schema{type: :string, description: "`list:LIST_ID`", example: "LIST:123"}
          ],
          description:
            "Visibility of the posted status. Besides standard MastoAPI values (`direct`, `private`, `unlisted` or `public`) it can be used to address a List by setting it to `list:LIST_ID`"
        },
        expires_in: %Schema{
          type: :integer,
          description:
            "The number of seconds the posted activity should expire in. When a posted activity expires it will be deleted from the server, and a delete request for it will be federated. This needs to be longer than an hour."
        },
        in_reply_to_conversation_id: %Schema{
          type: :string,
          description:
            "Will reply to a given conversation, addressing only the people who are part of the recipient set of that conversation. Sets the visibility to `direct`."
        }
      },
      example: %{
        "status" => "What time is it?",
        "sensitive" => "false",
        "poll" => %{
          "options" => ["Cofe", "Adventure"],
          "expires_in" => 420
        }
      }
    }
  end

  defp id_param do
    Operation.parameter(:id, :path, FlakeID, "Status ID",
      example: "9umDrYheeY451cQnEe",
      required: true
    )
  end

  defp status_response do
    Operation.response("Status", "application/json", Status)
  end

  defp context do
    %Schema{
      title: "StatusContext",
      description:
        "Represents the tree around a given status. Used for reconstructing threads of statuses.",
      type: :object,
      required: [:ancestors, :descendants],
      properties: %{
        ancestors: array_of_statuses(),
        descendants: array_of_statuses()
      },
      example: %{
        "ancestors" => [Status.schema().example],
        "descendants" => [Status.schema().example]
      }
    }
  end
end