<?php

namespace Illuminate\Database\Eloquent\Relations;

/**
 * @template TRelatedModel of \Illuminate\Database\Eloquent\Model
 * @template TDeclaringModel of \Illuminate\Database\Eloquent\Model
 *
 * @extends \Illuminate\Database\Eloquent\Relations\Relation<TRelatedModel, TDeclaringModel, \Illuminate\Database\Eloquent\Collection<int, TRelatedModel>>
 */
class BelongsToMany extends Relation
{
    /**
     * Find a related model by its primary key or return new instance of the related model.
     *
     * @param  mixed  $id
     * @param  array<int, (model-property<TRelatedModel>|'*')>|model-property<TRelatedModel>|'*'  $columns
     * @return ($id is (\Illuminate\Contracts\Support\Arrayable<array-key, mixed>|array<mixed>) ? \Illuminate\Database\Eloquent\Collection<int, TRelatedModel> : TRelatedModel)
     */
    public function findOrNew($id, $columns = ['*']);

    /**
     * Get the first related model record matching the attributes or instantiate it.
     *
     * @param  array<model-property<TRelatedModel>, mixed>  $attributes
     * @param  array<model-property<TRelatedModel>, mixed>  $values
     * @return TRelatedModel
     */
    public function firstOrNew(array $attributes = [], array $values = []);

    /**
     * Get the first related record matching the attributes or create it.
     *
     * @param  array<model-property<TRelatedModel>, mixed>  $attributes
     * @param  array<model-property<TRelatedModel>, mixed>  $values
     * @param  array<string, mixed>  $joining
     * @param  bool  $touch
     * @return TRelatedModel
     */
    public function firstOrCreate(array $attributes = [], array $values = [], array $joining = [], $touch = true);

    /**
     * Attempt to create the record. If a unique constraint violation occurs, attempt to find the matching record.
     *
     * @param  array<model-property<TRelatedModel>, mixed>  $attributes
     * @param  array<model-property<TRelatedModel>, mixed>  $values
     * @param  array<string, mixed>  $joining
     * @param  bool  $touch
     * @return TRelatedModel
     */
    public function createOrFirst(array $attributes, array $values = [], array $joining = [], $touch = true);

    /**
     * Create or update a related record matching the attributes, and fill it with values.
     *
     * @param  array<model-property<TRelatedModel>, mixed>  $attributes
     * @param  array<model-property<TRelatedModel>, mixed>  $values
     * @param  array<string, mixed>  $joining
     * @param  bool  $touch
     * @return TRelatedModel
     */
    public function updateOrCreate(array $attributes, array $values = [], array $joining = [], $touch = true);

    /**
     * Find a related model by its primary key.
     *
     * @param  mixed  $id
     * @param  array<int, (model-property<TRelatedModel>|'*')>  $columns
     * @return ($id is (\Illuminate\Contracts\Support\Arrayable<array-key, mixed>|array<mixed>) ? \Illuminate\Database\Eloquent\Collection<int, TRelatedModel> : TRelatedModel|null)
     */
    public function find($id, $columns = ['*']);

    /**
     * Find multiple related models by their primary keys.
     *
     * @param  \Illuminate\Contracts\Support\Arrayable<array-key, mixed>|int[]  $ids
     * @param  array<int, (model-property<TRelatedModel>|'*')>  $columns
     * @return \Illuminate\Database\Eloquent\Collection<int, TRelatedModel>
     */
    public function findMany($ids, $columns = ['*']);

    /**
     * Find a related model by its primary key or throw an exception.
     *
     * @param  mixed  $id
     * @param  array<int, (model-property<TRelatedModel>|'*')>  $columns
     * @return ($id is (\Illuminate\Contracts\Support\Arrayable<array-key, mixed>|array<mixed>) ? \Illuminate\Database\Eloquent\Collection<int, TRelatedModel> : TRelatedModel)
     *
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException
     */
    public function findOrFail($id, $columns = ['*']);

    /**
     * Execute the query and get the first result.
     *
     * @param  array<int, (model-property<TRelatedModel>|'*')>  $columns
     * @return TRelatedModel|null
     */
    public function first($columns = ['*']);

    /**
     * Execute the query and get the first result or throw an exception.
     *
     * @param  array<int, (model-property<TRelatedModel>|'*')>  $columns
     * @return TRelatedModel
     *
     * @throws \Illuminate\Database\Eloquent\ModelNotFoundException<TRelatedModel>
     */
    public function firstOrFail($columns = ['*']);

    /**
     * Create a new instance of the related model.
     *
     * @param  array<model-property<TRelatedModel>, mixed>  $attributes
     * @param  array<string, mixed>  $joining
     * @param  bool  $touch
     * @return TRelatedModel
     */
    public function create(array $attributes = [], array $joining = [], $touch = true);

    /**
     * Get a paginator for the "select" statement.
     *
     * @param  int|null  $perPage
     * @param  array<int, (model-property<TRelatedModel>|'*')>  $columns
     * @param  string  $pageName
     * @param  int|null  $page
     * @return \Illuminate\Pagination\LengthAwarePaginator<TRelatedModel>
     */
    public function paginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null);

    /**
     * Paginate the given query into a simple paginator.
     *
     * @param  int|null  $perPage
     * @param  array<int, (model-property<TRelatedModel>|'*')>  $columns
     * @param  string  $pageName
     * @param  int|null  $page
     * @return \Illuminate\Pagination\Paginator<TRelatedModel>
     */
    public function simplePaginate($perPage = null, $columns = ['*'], $pageName = 'page', $page = null);

    /**
     * Paginate the given query into a cursor paginator.
     *
     * @param  int|null  $perPage
     * @param  array<int, (model-property<TRelatedModel>|'*')>  $columns
     * @param  string  $cursorName
     * @param  string|null  $cursor
     * @return \Illuminate\Pagination\CursorPaginator<TRelatedModel>
     */
    public function cursorPaginate($perPage = null, $columns = ['*'], $cursorName = 'cursor', $cursor = null);
}
