<?php

namespace leetcode\util;

class MinHeap
{
    private array $heap;

    public function __construct()
    {
        $this->heap = [];
    }

    public function push(int $val): void
    {
        $this->heap[] = $val;
        $this->siftUp(count($this->heap) - 1);
    }

    public function pop()
    {
        if (empty($this->heap)) {
            throw new \InvalidArgumentException('Min heap is empty');
        }

        $leaf = array_pop($this->heap);

        if (empty($this->heap)) {
            return $leaf;
        }
        $value = $this->getRoot();
        $this->setRoot($leaf);

        return $value;
    }

    public function count(): int
    {
        return count($this->heap);
    }

    public function top()
    {
        return current($this->heap);
    }

    public function compare(int $a, int $b): int
    {
        return $a <=> $b;
    }

    public function getRoot(): int
    {
        return $this->heap[0];
    }

    public function setRoot(int $leaf): void
    {
        $this->heap[0] = $leaf;
        $this->siftDown(0);
    }

    private function siftUp(int $leaf): void
    {
        while ($leaf > 0) {
            $parent = $this->parent($leaf);
            if ($this->compare($this->heap[$leaf], $this->heap[$parent]) > 0) {
                break;
            }
            [$this->heap[$leaf], $this->heap[$parent]] = [$this->heap[$parent], $this->heap[$leaf]];
            $leaf = $parent;
        }
    }

    private function siftDown(int $node): void
    {
        $last = (int) floor(count($this->heap) / 2);

        for ($parent = $node; $parent < $last; $parent = $leaf) {
            $leaf = $this->getSmallestLeaf($parent);
            if ($this->compare($this->heap[$parent], $this->heap[$leaf]) < 0) {
                break;
            }
            [$this->heap[$leaf], $this->heap[$parent]] = [$this->heap[$parent], $this->heap[$leaf]];
        }
    }

    private function getSmallestLeaf(int $parent): int
    {
        $left = $this->left($parent);
        $right = $this->right($parent);

        if ($right < count($this->heap) && $this->compare($this->heap[$left], $this->heap[$right]) > 0) {
            return $right;
        }

        return $left;
    }

    private function left(int $index): int
    {
        return ($index * 2) + 1;
    }

    private function right(int $index): int
    {
        return ($index * 2) + 2;
    }

    private function parent(int $index): int
    {
        return ($index - 1) / 2;
    }
}