<commit_before><?php

/**
 *    Copyright (c) ppy Pty Ltd <<EMAIL>>.
 *
 *    This file is part of osu!web. osu!web is distributed with the hope of
 *    attracting more community contributions to the core ecosystem of osu!.
 *
 *    osu!web is free software: you can redistribute it and/or modify
 *    it under the terms of the Affero GNU General Public License version 3
 *    as published by the Free Software Foundation.
 *
 *    osu!web is distributed WITHOUT ANY WARRANTY; without even the implied
 *    warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *    See the GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
 */

namespace App\Libraries;

use App\Models\Comment;
use App\Models\CommentVote;
use App\Models\User;

class CommentBundle
{
    public $depth;
    public $includeCommentableMeta;
    public $params;

    private $commentable;
    private $comment;
    private $user;

    public static function forComment(Comment $comment, bool $includeNested = false)
    {
        $options = [
            'comment' => $comment,
            'includeCommentableMeta' => true,
        ];

        if ($includeNested) {
            $options['params'] = ['parent_id' => $comment->getKey()];
        }

        return new static($comment->commentable, $options);
    }

    public static function forEmbed($commentable)
    {
        return new static($commentable, ['params' => ['parent_id' => 0]]);
    }

    public function __construct($commentable, array $options = [])
    {
        $this->commentable = $commentable;

        $this->user = $options['user'] ?? auth()->user();

        $this->params = new CommentBundleParams($options['params'] ?? [], $this->user);

        $this->comment = $options['comment'] ?? null;
        $this->depth = $options['depth'] ?? 2;
        $this->includeCommentableMeta = $options['includeCommentableMeta'] ?? false;
        $this->includeDeleted = $options['includeDeleted'] ?? true;
        $this->includePinned = $options['includePinned'] ?? true;
    }

    public function toArray()
    {
        $hasMore = false;
        $includedComments = collect();

        // Either use the provided comment as a base, or look for matching comments.
        if (isset($this->comment)) {
            $comments = collect([$this->comment]);
            if ($this->comment->parent !== null) {
                $includedComments->push($this->comment->parent);
            }
        } else {
            $comments = $this->getComments($this->commentsQuery(), false);
            if ($comments->count() > $this->params->limit) {
                $hasMore = true;
                $comments->pop();
            }
        }

        $commentIds = $comments->pluck('id');

        // Get parents when listing comments index
        if ($this->commentable === null) {
            $parents = $this->getComments(Comment::whereIn('id', $comments->pluck('parent_id')));
            $includedComments = $includedComments->concat($parents);
        }

        // Get nested comments
        if ($this->params->parentId !== null) {
            $nestedParentIds = $commentIds;

            for ($i = 0; $i < $this->depth; $i++) {
                $nestedComments = $this->getComments(Comment::whereIn('parent_id', $nestedParentIds));
                $nestedParentIds = $nestedComments->pluck('id');
                $includedComments = $includedComments->concat($nestedComments);
            }

            $parents = Comment::whereIn('id', $comments->pluck('parent_id'))->get();
            $includedComments = $includedComments->concat($parents);
        }

        $includedComments = $includedComments->unique('id', true)->reject(function ($comment) use ($commentIds) {
            return $commentIds->contains($comment->getKey());
        });
        $allComments = $comments->concat($includedComments);

        if ($this->includePinned) {
            $pinnedComments = $this->getComments($this->commentsQuery()->where('pinned', true));
        }

        $result = [
            'comments' => json_collection($comments, 'Comment'),
            'has_more' => $hasMore,
            'has_more_id' => $this->params->parentId,
            'included_comments' => json_collection($includedComments, 'Comment'),
            'pinned_comments' => json_collection($pinnedComments ?? [], 'Comment'),
            'user_votes' => $this->getUserVotes($allComments),
            'user_follow' => $this->getUserFollow(),
            'users' => json_collection($this->getUsers($comments->concat($allComments)), 'UserCompact'),
            'sort' => $this->params->sort,
        ];

        if ($this->params->parentId === 0 || $this->params->parentId === null) {
            $result['top_level_count'] = $this->commentsQuery()->whereNull('parent_id')->count();
            $result['total'] = $this->commentsQuery()->count();
        }

        if ($this->includeCommentableMeta) {
            $commentables = $comments->pluck('commentable')->concat([null]);
            $result['commentable_meta'] = json_collection($commentables, 'CommentableMeta');
        }

        return $result;
    }

    public function commentsQuery()
    {
        if (isset($this->commentable)) {
            return $this->commentable->comments();
        } else {
            return Comment::select();
        }
    }

    private function getComments($query, $isChildren = true)
    {
        $sort = $this->params->sortDbOptions();
        $sorted = false;
        $queryLimit = $this->params->limit;

        if (!$isChildren) {
            if ($this->params->filterByParentId()) {
                $query->where(['parent_id' => $this->params->parentIdForWhere()]);
            }

            $queryLimit++;
            $queryCursor = [];
            $hasValidCursor = true;

            foreach ($sort as $column => $order) {
                $key = $column === 'votes_count_cache' ? 'votesCount' : camel_case($column);
                $value = $this->params->cursor[$key];
                if (isset($value)) {<commit_msg>Always sort pinned comments by new<commit_after><?php

/**
 *    Copyright (c) ppy P<NAME> <<EMAIL>>.
 *
 *    This file is part of osu!web. osu!web is distributed with the hope of
 *    attracting more community contributions to the core ecosystem of osu!.
 *
 *    osu!web is free software: you can redistribute it and/or modify
 *    it under the terms of the Affero GNU General Public License version 3
 *    as published by the Free Software Foundation.
 *
 *    osu!web is distributed WITHOUT ANY WARRANTY; without even the implied
 *    warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 *    See the GNU Affero General Public License for more details.
 *
 *    You should have received a copy of the GNU Affero General Public License
 *    along with osu!web.  If not, see <http://www.gnu.org/licenses/>.
 */

namespace App\Libraries;

use App\Models\Comment;
use App\Models\CommentVote;
use App\Models\User;

class CommentBundle
{
    public $depth;
    public $includeCommentableMeta;
    public $params;

    private $commentable;
    private $comment;
    private $user;

    public static function forComment(Comment $comment, bool $includeNested = false)
    {
        $options = [
            'comment' => $comment,
            'includeCommentableMeta' => true,
        ];

        if ($includeNested) {
            $options['params'] = ['parent_id' => $comment->getKey()];
        }

        return new static($comment->commentable, $options);
    }

    public static function forEmbed($commentable)
    {
        return new static($commentable, ['params' => ['parent_id' => 0]]);
    }

    public function __construct($commentable, array $options = [])
    {
        $this->commentable = $commentable;

        $this->user = $options['user'] ?? auth()->user();

        $this->params = new CommentBundleParams($options['params'] ?? [], $this->user);

        $this->comment = $options['comment'] ?? null;
        $this->depth = $options['depth'] ?? 2;
        $this->includeCommentableMeta = $options['includeCommentableMeta'] ?? false;
        $this->includeDeleted = $options['includeDeleted'] ?? true;
        $this->includePinned = $options['includePinned'] ?? true;
    }

    public function toArray()
    {
        $hasMore = false;
        $includedComments = collect();

        // Either use the provided comment as a base, or look for matching comments.
        if (isset($this->comment)) {
            $comments = collect([$this->comment]);
            if ($this->comment->parent !== null) {
                $includedComments->push($this->comment->parent);
            }
        } else {
            $comments = $this->getComments($this->commentsQuery(), false);
            if ($comments->count() > $this->params->limit) {
                $hasMore = true;
                $comments->pop();
            }
        }

        $commentIds = $comments->pluck('id');

        // Get parents when listing comments index
        if ($this->commentable === null) {
            $parents = $this->getComments(Comment::whereIn('id', $comments->pluck('parent_id')));
            $includedComments = $includedComments->concat($parents);
        }

        // Get nested comments
        if ($this->params->parentId !== null) {
            $nestedParentIds = $commentIds;

            for ($i = 0; $i < $this->depth; $i++) {
                $nestedComments = $this->getComments(Comment::whereIn('parent_id', $nestedParentIds));
                $nestedParentIds = $nestedComments->pluck('id');
                $includedComments = $includedComments->concat($nestedComments);
            }

            $parents = Comment::whereIn('id', $comments->pluck('parent_id'))->get();
            $includedComments = $includedComments->concat($parents);
        }

        $includedComments = $includedComments->unique('id', true)->reject(function ($comment) use ($commentIds) {
            return $commentIds->contains($comment->getKey());
        });
        $allComments = $comments->concat($includedComments);

        if ($this->includePinned) {
            $pinnedComments = $this->getComments($this->commentsQuery()->where('pinned', true), true, 'new');
        }

        $result = [
            'comments' => json_collection($comments, 'Comment'),
            'has_more' => $hasMore,
            'has_more_id' => $this->params->parentId,
            'included_comments' => json_collection($includedComments, 'Comment'),
            'pinned_comments' => json_collection($pinnedComments ?? [], 'Comment'),
            'user_votes' => $this->getUserVotes($allComments),
            'user_follow' => $this->getUserFollow(),
            'users' => json_collection($this->getUsers($comments->concat($allComments)), 'UserCompact'),
            'sort' => $this->params->sort,
        ];

        if ($this->params->parentId === 0 || $this->params->parentId === null) {
            $result['top_level_count'] = $this->commentsQuery()->whereNull('parent_id')->count();
            $result['total'] = $this->commentsQuery()->count();
        }

        if ($this->includeCommentableMeta) {
            $commentables = $comments->pluck('commentable')->concat([null]);
            $result['commentable_meta'] = json_collection($commentables, 'CommentableMeta');
        }

        return $result;
    }

    public function commentsQuery()
    {
        if (isset($this->commentable)) {
            return $this->commentable->comments();
        } else {
            return Comment::select();
        }
    }

    private function getComments($query, $isChildren = true, $customSort = null)
    {
        $sort = $customSort ? CommentBundleParams::SORTS[$customSort] : $this->params->sortDbOptions();
        $sorted = false;
        $queryLimit = $this->params->limit;

        if (!$isChildren) {
            if ($this->params->filterByParentId()) {
                $query->where(['parent_id' => $this->params->parentIdForWhere()]);
            }

            $queryLimit++;
            $queryCursor = [];
            $hasValidCursor = true;

            foreach ($sort as $column => $order) {
                $key = $column === 'votes_count_cache' ? 'votesCount' : camel_case($column);
                $value = $this->params->cursor[$key];
                if (isset($value)) {
{'fraction_non_alphanumeric': 0.1528044871794872, 'fraction_numerical': 0.0008012820512820513, 'mean_word_length': 2.4439845474613686, 'pattern_counts': {'":': 0, '<': 14, '<?xml version=': 0, '>': 208, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '339132', 'n_tokens_mistral': 3460, 'n_tokens_neox': 3305, 'n_words': 756}