<?php

/**
 * LolaCore - Skill value object
 *
 * Immutable representation of a parsed and validated Skill. Built from
 * the frontmatter array produced by YamlParser + SkillValidator and the
 * raw markdown body.
 *
 * @package LolaCore
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\Skills;

class Skill
{
    public const ORIGIN_BUILT_IN   = 'built_in';
    public const ORIGIN_ADDON      = 'addon';
    public const ORIGIN_FILESYSTEM = 'filesystem';
    public const ORIGIN_DB         = 'db';

    public const STATUS_ACTIVE              = 'active';
    // Persisted: the user turned the skill off via the toggle. Reversible.
    public const STATUS_INACTIVE            = 'inactive';
    // Computed at runtime: the skill is active but has no activation rules
    // (no `always`, no triggers, no conditions) so it can never fire.
    // Surfaces in the UI as an informational badge; not a state the user
    // sets directly.
    public const STATUS_NO_TRIGGERS         = 'no-triggers';
    public const STATUS_INCOMPATIBLE        = 'incompatible';
    public const STATUS_UNMET_DEPENDENCIES  = 'unmet-dependencies';

    private const DEFAULT_TRIGGERS = [
        'keywords' => [],
        'regex'    => [],
        'intents'  => [],
    ];

    private const DEFAULT_CONDITIONS = [
        'requires_addons'  => [],
        'requires_plugins' => [],
        'user_capability'  => '',
        'locale'           => [],
    ];

    private string $name;
    private string $description;
    private string $body;
    private string $origin;
    private bool $always;
    /** @var array{keywords: array<int,string>, regex: array<int,string>, intents: array<int,string>} */
    private array $triggers;
    /** @var array{requires_addons: array<int,string>, requires_plugins: array<int,string>, user_capability: string, locale: array<int,string>} */
    private array $conditions;
    private int $priority;
    private string $minLolaVersion;
    private bool $writesData;
    private ?string $version;
    private ?string $author;
    private string $status;

    public function __construct(
        string $name,
        string $description,
        string $body,
        string $origin,
        bool $always = false,
        array $triggers = [],
        array $conditions = [],
        int $priority = 50,
        string $minLolaVersion = '',
        bool $writesData = false,
        ?string $version = null,
        ?string $author = null,
        string $status = self::STATUS_ACTIVE
    ) {
        $this->name           = $name;
        $this->description    = $description;
        $this->body           = $body;
        $this->origin         = $origin;
        $this->always         = $always;
        $this->triggers       = array_merge(self::DEFAULT_TRIGGERS, $triggers);
        $this->conditions     = array_merge(self::DEFAULT_CONDITIONS, $conditions);
        $this->priority       = $priority;
        $this->minLolaVersion = $minLolaVersion;
        $this->writesData     = $writesData;
        $this->version        = $version;
        $this->author         = $author;
        $this->status         = $status;
    }

    /**
     * Factory from the parser+validator output.
     *
     * @param array<string,mixed> $frontmatter Validated frontmatter array.
     * @param string              $body        Verbatim markdown body.
     * @param string              $origin      One of the ORIGIN_* constants.
     */
    public static function fromParsed(array $frontmatter, string $body, string $origin): self
    {
        $triggers = is_array($frontmatter['triggers'] ?? null)
            ? $frontmatter['triggers']
            : [];

        $conditions = is_array($frontmatter['conditions'] ?? null)
            ? $frontmatter['conditions']
            : [];

        return new self(
            name: (string) ($frontmatter['name'] ?? ''),
            description: (string) ($frontmatter['description'] ?? ''),
            body: $body,
            origin: $origin,
            always: (bool) ($frontmatter['always'] ?? false),
            triggers: $triggers,
            conditions: $conditions,
            priority: (int) ($frontmatter['priority'] ?? 50),
            minLolaVersion: (string) ($frontmatter['min_lolacore_version'] ?? ''),
            writesData: (bool) ($frontmatter['writes_data'] ?? false),
            version: isset($frontmatter['version']) && $frontmatter['version'] !== null
                ? (string) $frontmatter['version']
                : null,
            author: isset($frontmatter['author']) && $frontmatter['author'] !== null
                ? (string) $frontmatter['author']
                : null,
            status: (string) ($frontmatter['status'] ?? self::STATUS_ACTIVE),
        );
    }

    public function getName(): string
    {
        return $this->name;
    }

    public function getDescription(): string
    {
        return $this->description;
    }

    public function getBody(): string
    {
        return $this->body;
    }

    public function getOrigin(): string
    {
        return $this->origin;
    }

    public function isAlways(): bool
    {
        return $this->always;
    }

    /**
     * @return array{keywords: array<int,string>, regex: array<int,string>, intents: array<int,string>}
     */
    public function getTriggers(): array
    {
        return $this->triggers;
    }

    /**
     * @return array{requires_addons: array<int,string>, requires_plugins: array<int,string>, user_capability: string, locale: array<int,string>}
     */
    public function getConditions(): array
    {
        return $this->conditions;
    }

    public function getPriority(): int
    {
        return $this->priority;
    }

    public function getMinLolaVersion(): string
    {
        return $this->minLolaVersion;
    }

    public function writesData(): bool
    {
        return $this->writesData;
    }

    public function getVersion(): ?string
    {
        return $this->version;
    }

    public function getAuthor(): ?string
    {
        return $this->author;
    }

    public function getStatus(): string
    {
        return $this->status;
    }

    /**
     * The skill is considered fire-eligible iff its persisted status is
     * STATUS_ACTIVE. Runtime computed statuses (no-triggers, incompatible,
     * unmet-dependencies) and the user-paused state (inactive) all evaluate
     * to false here.
     */
    public function isActivatable(): bool
    {
        return $this->status === self::STATUS_ACTIVE;
    }

    /**
     * Return a copy with the given status.
     */
    public function withStatus(string $status): self
    {
        $clone         = clone $this;
        $clone->status = $status;
        return $clone;
    }

    /**
     * Return a copy with always overridden (used by Layer D user-forced skills).
     */
    public function withAlways(bool $always): self
    {
        $clone         = clone $this;
        $clone->always = $always;
        return $clone;
    }

    /**
     * Serialize to a portable array for cache storage.
     *
     * @return array<string,mixed>
     */
    public function toArray(): array
    {
        return [
            'name'                 => $this->name,
            'description'          => $this->description,
            'body'                 => $this->body,
            'origin'               => $this->origin,
            'always'               => $this->always,
            'triggers'             => $this->triggers,
            'conditions'           => $this->conditions,
            'priority'             => $this->priority,
            'min_lolacore_version' => $this->minLolaVersion,
            'writes_data'          => $this->writesData,
            'version'              => $this->version,
            'author'               => $this->author,
            'status'               => $this->status,
        ];
    }

    /**
     * Reconstruct from a previously serialized array.
     *
     * @param array<string,mixed> $data
     */
    public static function fromArray(array $data): self
    {
        $triggers   = is_array($data['triggers'] ?? null) ? $data['triggers'] : [];
        $conditions = is_array($data['conditions'] ?? null) ? $data['conditions'] : [];

        return new self(
            name: (string) ($data['name'] ?? ''),
            description: (string) ($data['description'] ?? ''),
            body: (string) ($data['body'] ?? ''),
            origin: (string) ($data['origin'] ?? self::ORIGIN_BUILT_IN),
            always: (bool) ($data['always'] ?? false),
            triggers: $triggers,
            conditions: $conditions,
            priority: (int) ($data['priority'] ?? 50),
            minLolaVersion: (string) ($data['min_lolacore_version'] ?? ''),
            writesData: (bool) ($data['writes_data'] ?? false),
            version: isset($data['version']) && $data['version'] !== null ? (string) $data['version'] : null,
            author: isset($data['author']) && $data['author'] !== null ? (string) $data['author'] : null,
            status: (string) ($data['status'] ?? self::STATUS_ACTIVE),
        );
    }

    public function hasActivationRules(): bool
    {
        if ($this->always) {
            return true;
        }

        if (!empty($this->triggers['keywords']) || !empty($this->triggers['regex']) || !empty($this->triggers['intents'])) {
            return true;
        }

        if (!empty($this->conditions['requires_addons'])
            || !empty($this->conditions['requires_plugins'])
            || $this->conditions['user_capability'] !== ''
            || !empty($this->conditions['locale'])
        ) {
            return true;
        }

        return false;
    }
}
