<?php
/**
 * Model Collection Class
 *
 * @package ZenoChatbot\Framework\Database
 */

namespace ZenoChatbot\Framework\Database;

use ZenoChatbot\Framework\Support\Collection;

// Prevent direct access
if (!defined('ABSPATH')) {
    exit;
}

/**
 * Collection class specifically for Model instances
 */
class ModelCollection extends Collection
{
    /**
     * Save all models in the collection
     *
     * @return bool
     */
    public function save()
    {
        $success = true;

        foreach ($this->items as $model) {
            if ($model instanceof Model) {
                if (!$model->save()) {
                    $success = false;
                }
            }
        }

        return $success;
    }

    /**
     * Delete all models in the collection
     *
     * @return bool
     */
    public function delete()
    {
        $success = true;

        foreach ($this->items as $model) {
            if ($model instanceof Model) {
                if (!$model->delete()) {
                    $success = false;
                }
            }
        }

        return $success;
    }

    /**
     * Load a relation for all models in the collection
     *
     * @param string $relation
     * @return $this
     */
    public function load($relation)
    {
        if ($this->isEmpty()) {
            return $this;
        }

        // Get the first model to determine the relation type
        $firstModel = $this->first();
        if (!$firstModel instanceof Model) {
            return $this;
        }

        // This is a simplified implementation
        // In a full implementation, you would handle different relation types
        foreach ($this->items as $model) {
            if ($model instanceof Model && method_exists($model, $relation)) {
                $model->$relation();
            }
        }

        return $this;
    }

    /**
     * Get the primary keys of all models in the collection
     *
     * @return Collection
     */
    public function modelKeys()
    {
        return $this->map(function ($model) {
            return $model instanceof Model ? $model->getKey() : null;
        })->filter();
    }

    /**
     * Find a model in the collection by key
     *
     * @param mixed $key
     * @return Model|null
     */
    public function find($key)
    {
        return $this->first(function ($model) use ($key) {
            return $model instanceof Model && $model->getKey() == $key;
        });
    }

    /**
     * Determine if a model exists in the collection by key
     *
     * @param mixed $key
     * @return bool
     */
    public function contains($key)
    {
        if (func_num_args() === 1 && !is_callable($key)) {
            return !is_null($this->find($key));
        }

        return parent::contains($key);
    }

    /**
     * Get the array of primary keys
     *
     * @return array
     */
    public function getKeys()
    {
        return $this->modelKeys()->toArray();
    }

    /**
     * Merge the collection with the given items
     *
     * @param mixed $items
     * @return static
     */
    public function merge($items)
    {
        $merged = parent::merge($items);

        return new static($merged->all());
    }

    /**
     * Get the collection of items as a plain array
     *
     * @return array
     */
    public function toArray()
    {
        return $this->map(function ($model) {
            return $model instanceof Model ? $model->toArray() : $model;
        })->all();
    }

    /**
     * Get the collection as JSON
     *
     * @param int $options
     * @return string
     */
    public function toJson($options = 0)
    {
        return json_encode($this->toArray(), $options);
    }

    /**
     * Add an item to the collection
     *
     * @param mixed $item
     * @return $this
     */
    public function add($item)
    {
        $this->items[] = $item;
        return $this;
    }

    /**
     * Get unique models based on the given key
     *
     * @param string|callable|null $key
     * @param bool $strict
     * @return static
     */
    public function unique($key = null, $strict = false)
    {
        if (is_null($key)) {
            $key = function ($model) {
                return $model instanceof Model ? $model->getKey() : $model;
            };
        }

        $unique = parent::unique($key, $strict);

        return new static($unique->all());
    }

    /**
     * Get models with only the specified attributes
     *
     * @param array $attributes
     * @return static
     */
    public function only($attributes)
    {
        return $this->map(function ($model) use ($attributes) {
            if (!$model instanceof Model) {
                return $model;
            }

            $filtered = [];
            foreach ($attributes as $attribute) {
                $filtered[$attribute] = $model->getAttribute($attribute);
            }

            return $filtered;
        });
    }

    /**
     * Get models without the specified attributes
     *
     * @param array $attributes
     * @return static
     */
    public function except($attributes)
    {
        return $this->map(function ($model) use ($attributes) {
            if (!$model instanceof Model) {
                return $model;
            }

            $modelArray = $model->toArray();
            foreach ($attributes as $attribute) {
                unset($modelArray[$attribute]);
            }

            return $modelArray;
        });
    }

    /**
     * Make the given, typically visible, attributes hidden across the entire collection
     *
     * @param array|string $attributes
     * @return $this
     */
    public function makeHidden($attributes)
    {
        $attributes = is_array($attributes) ? $attributes : func_get_args();

        return $this->each(function ($model) use ($attributes) {
            if ($model instanceof Model) {
                foreach ($attributes as $attribute) {
                    $model->addHidden($attribute);
                }
            }
        });
    }

    /**
     * Make the given, typically hidden, attributes visible across the entire collection
     *
     * @param array|string $attributes
     * @return $this
     */
    public function makeVisible($attributes)
    {
        $attributes = is_array($attributes) ? $attributes : func_get_args();

        return $this->each(function ($model) use ($attributes) {
            if ($model instanceof Model) {
                foreach ($attributes as $attribute) {
                    $model->makeVisible($attribute);
                }
            }
        });
    }

    /**
     * Append attributes to each model in the collection
     *
     * @param array|string $attributes
     * @return $this
     */
    public function append($attributes)
    {
        $attributes = is_array($attributes) ? $attributes : func_get_args();

        return $this->each(function ($model) use ($attributes) {
            if ($model instanceof Model) {
                foreach ($attributes as $attribute) {
                    $model->append($attribute);
                }
            }
        });
    }

    /**
     * Get a dictionary keyed by the given attribute
     *
     * @param string $keyBy
     * @return Collection
     */
    public function getDictionary($keyBy = null)
    {
        $keyBy = $keyBy ?: function ($model) {
            return $model instanceof Model ? $model->getKey() : null;
        };

        return $this->keyBy($keyBy);
    }

    /**
     * Reload all models in the collection from the database
     *
     * @return $this
     */
    public function fresh()
    {
        if ($this->isEmpty()) {
            return $this;
        }

        $freshModels = $this->map(function ($model) {
            if (!$model instanceof Model) {
                return $model;
            }

            $modelClass = get_class($model);
            return $modelClass::find($model->getKey());
        })->filter();

        return new static($freshModels->all());
    }

    /**
     * Diff the collection with the given items
     *
     * @param mixed $items
     * @return static
     */
    public function diff($items)
    {
        $diffed = parent::diff($items);

        return new static($diffed->all());
    }

    /**
     * Intersect the collection with the given items
     *
     * @param mixed $items
     * @return static
     */
    public function intersect($items)
    {
        $intersected = parent::intersect($items);

        return new static($intersected->all());
    }
}
