<?php

/**
 * PlaybooksHandler — Domain handler for Lola's Playbooks actions.
 *
 * Owns: create_playbook, list_playbooks, read_playbook, update_playbook,
 *       delete_playbook, activate_playbook, deactivate_playbook.
 *
 * @package LolaCore\Actions\Handlers
 * @license GPL-2.0-or-later
 */

declare(strict_types=1);

namespace LolaCore\Actions\Handlers;

// phpcs:disable WordPress.Security.EscapeOutput.ExceptionNotEscaped -- Diagnostic exception messages; escaped at REST/chat presentation layer.

use LolaCore\Actions\ActionLogger;
use LolaCore\Actions\Contracts\ActionHandlerInterface;
use LolaCore\Playbooks\Exceptions\PlaybookParseError;
use LolaCore\Playbooks\Exceptions\PlaybookValidationError;
use LolaCore\Playbooks\Playbook;
use LolaCore\Playbooks\PlaybookLoader;
use LolaCore\Playbooks\PlaybookRegistry;
use LolaCore\Playbooks\PlaybookStore;
use LolaCore\Playbooks\PlaybookValidator;
use LolaCore\Skills\YamlParser;

if (!defined('ABSPATH')) {
    exit;
}

class PlaybooksHandler implements ActionHandlerInterface
{
    public function getSupportedActions(): array
    {
        return [
            'create_playbook',
            'list_playbooks',
            'read_playbook',
            'update_playbook',
            'delete_playbook',
            'activate_playbook',
            'deactivate_playbook',
        ];
    }

    public function handle(string $actionId, array $params, bool $dryRun): array
    {
        return match ($actionId) {
            'create_playbook'     => $this->createPlaybook($params),
            'list_playbooks'      => $this->listPlaybooks($params),
            'read_playbook'       => $this->readPlaybook($params),
            'update_playbook'     => $this->updatePlaybook($params, $dryRun),
            'delete_playbook'     => $this->deletePlaybook($params, $dryRun),
            'activate_playbook'   => $this->activatePlaybook($params, $dryRun),
            'deactivate_playbook' => $this->deactivatePlaybook($params, $dryRun),
            default               => throw new \Exception(esc_html("PlaybooksHandler: unknown action '{$actionId}'.")),
        };
    }

    private function createPlaybook(array $params): array
    {
        if (!current_user_can('manage_options')) {
            throw new \Exception( __( 'Insufficient permissions to create playbooks.', 'lolacore' ) );
        }

        $slug    = isset($params['playbook_slug']) ? sanitize_title((string) $params['playbook_slug']) : '';
        $content = isset($params['content']) ? (string) $params['content'] : '';

        if ($slug === '') {
            throw new \Exception( __( 'playbook_slug is required and must resolve to a valid kebab-case identifier.', 'lolacore' ) );
        }
        if ($content === '') {
            throw new \Exception( __( 'content is required.', 'lolacore' ) );
        }

        // Registered as type=read in ActionExecutor — dryRun is ignored; inactive state is the safety gate.
        try {
            $id = $this->buildStore()->create($slug, $content);
        } catch (PlaybookParseError|PlaybookValidationError $e) {
            // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- ActionExecutor surfaces messages through escaped API responses.
            throw new \Exception($e->getMessage());
        }

        $logger = new ActionLogger();
        $logger->log(
            'create_playbook',
            sprintf('Playbook "%s" created in inactive state (id: %d).', $slug, $id),
            'success',
            'chat',
            ['playbook_slug' => $slug, 'id' => $id]
        );

        return [
            'success'       => true,
            'playbook_slug' => $slug,
            'id'            => $id,
            /* translators: 1: value */
            'message'       => sprintf( __( 'Playbook "%s" saved (inactive). Ask me to activate it when you are ready.', 'lolacore' ), $slug ),
        ];
    }

    private function listPlaybooks(array $params): array
    {
        if (!current_user_can('manage_options')) {
            throw new \Exception( __( 'Insufficient permissions to list playbooks.', 'lolacore' ) );
        }

        $statusFilter = isset($params['status']) ? sanitize_text_field((string) $params['status']) : '';
        $registry     = $this->buildRegistry();
        $bundled      = [Playbook::ORIGIN_BUILT_IN, Playbook::ORIGIN_ADDON, Playbook::ORIGIN_FILESYSTEM];
        $out          = [];
        $counts       = ['active' => 0, 'inactive' => 0, 'archived' => 0, 'other' => 0];

        foreach ($registry->all() as $playbook) {
            $status = $playbook->getStatus();
            $origin = $playbook->getOrigin();

            if ($statusFilter !== '' && $status !== $statusFilter) {
                continue;
            }

            $bucket            = isset($counts[$status]) ? $status : 'other';
            $counts[$bucket]   = $counts[$bucket] + 1;

            $out[] = [
                'slug'        => $playbook->getSlug(),
                'title'       => $playbook->getDescription(),
                'domain'      => $playbook->getDomain(),
                'type'        => $playbook->getType(),
                'status'      => $status,
                'origin'      => $origin,
                'manageable'  => !in_array($origin, $bundled, true),
                'effective'   => $playbook->isActivatable(),
            ];
        }

        return [
            'total'     => count($out),
            'counts'    => $counts,
            'playbooks' => $out,
        ];
    }

    private function readPlaybook(array $params): array
    {
        if (!current_user_can('manage_options')) {
            throw new \Exception( __( 'Insufficient permissions to read playbooks.', 'lolacore' ) );
        }

        $slug = isset($params['playbook_slug']) ? sanitize_title((string) $params['playbook_slug']) : '';
        if ($slug === '') {
            throw new \Exception( __( 'playbook_slug is required.', 'lolacore' ) );
        }

        $registry = $this->buildRegistry();
        $playbook = $registry->getBySlug($slug);
        if ($playbook === null) {
            /* translators: 1: value */
            throw new \Exception( sprintf( __( 'No playbook found with slug "%s".', 'lolacore' ), esc_html( $slug ) ) );
        }

        $bundled = [Playbook::ORIGIN_BUILT_IN, Playbook::ORIGIN_ADDON, Playbook::ORIGIN_FILESYSTEM];
        $content = $this->buildLoader()->readRawContentBySlug($slug);

        return [
            'success'       => true,
            'playbook_slug' => $playbook->getSlug(),
            'title'         => $playbook->getDescription(),
            'domain'        => $playbook->getDomain(),
            'type'          => $playbook->getType(),
            'status'        => $playbook->getStatus(),
            'origin'        => $playbook->getOrigin(),
            'manageable'    => !in_array($playbook->getOrigin(), $bundled, true),
            'body'          => $playbook->getBody(),
            'content'       => $content ?? '',
        ];
    }

    private function updatePlaybook(array $params, bool $dryRun): array
    {
        if (!current_user_can('manage_options')) {
            throw new \Exception( __( 'Insufficient permissions to update playbooks.', 'lolacore' ) );
        }

        $slug    = isset($params['playbook_slug']) ? sanitize_title((string) $params['playbook_slug']) : '';
        $content = isset($params['content']) ? (string) $params['content'] : '';

        if ($slug === '') {
            throw new \Exception( __( 'playbook_slug is required.', 'lolacore' ) );
        }
        if ($content === '') {
            throw new \Exception( __( 'content is required.', 'lolacore' ) );
        }

        $row = $this->requireDbRow($slug);
        $id  = (int) ($row['id'] ?? 0);
        $status = (string) ($row['status'] ?? '');

        if ($status === Playbook::STATUS_ARCHIVED) {
            /* translators: 1: value */
            throw new \Exception( sprintf( __( 'Playbook "%s" is archived and cannot be updated. Restore it first.', 'lolacore' ), esc_html( $slug ) ) );
        }

        if ($dryRun) {
            return [
                'preview'       => true,
                'playbook_slug' => $slug,
                'status'        => $status,
                /* translators: 1: value, 2: value */
                'message'       => sprintf( __( 'Will replace the source of playbook "%1$s" (currently %2$s). The status will be preserved.', 'lolacore' ), $slug, $status ),
            ];
        }

        try {
            $this->buildStore()->update($id, $content);
        } catch (PlaybookParseError|PlaybookValidationError $e) {
            // phpcs:ignore WordPress.Security.EscapeOutput.ExceptionNotEscaped -- ActionExecutor surfaces messages through escaped API responses.
            throw new \Exception($e->getMessage());
        }

        $logger = new ActionLogger();
        $logger->log(
            'update_playbook',
            sprintf('Playbook "%s" updated (id: %d, status preserved: %s).', $slug, $id, $status),
            'success',
            'chat',
            ['playbook_slug' => $slug, 'id' => $id, 'status' => $status]
        );

        return [
            'success'       => true,
            'playbook_slug' => $slug,
            'id'            => $id,
            'status'        => $status,
            /* translators: 1: value */
            'message'       => sprintf( __( 'Playbook "%s" updated.', 'lolacore' ), $slug ),
        ];
    }

    private function deletePlaybook(array $params, bool $dryRun): array
    {
        if (!current_user_can('manage_options')) {
            throw new \Exception( __( 'Insufficient permissions to delete playbooks.', 'lolacore' ) );
        }

        $slug = isset($params['playbook_slug']) ? sanitize_title((string) $params['playbook_slug']) : '';
        if ($slug === '') {
            throw new \Exception( __( 'playbook_slug is required.', 'lolacore' ) );
        }

        $row = $this->requireDbRow($slug);
        $id  = (int) ($row['id'] ?? 0);
        $status = (string) ($row['status'] ?? '');

        if ($dryRun) {
            return [
                'preview'       => true,
                'playbook_slug' => $slug,
                'status'        => $status,
                /* translators: 1: value, 2: value */
                'message'       => sprintf( __( 'Will permanently delete playbook "%1$s" (currently %2$s). This cannot be undone.', 'lolacore' ), $slug, $status ),
            ];
        }

        $this->buildStore()->delete($id);

        $logger = new ActionLogger();
        $logger->log(
            'delete_playbook',
            sprintf('Playbook "%s" deleted (id: %d, was %s).', $slug, $id, $status),
            'success',
            'chat',
            ['playbook_slug' => $slug, 'id' => $id, 'previous_status' => $status]
        );

        return [
            'success'       => true,
            'playbook_slug' => $slug,
            /* translators: 1: value */
            'message'       => sprintf( __( 'Playbook "%s" deleted.', 'lolacore' ), $slug ),
        ];
    }

    private function activatePlaybook(array $params, bool $dryRun): array
    {
        if (!current_user_can('manage_options')) {
            throw new \Exception( __( 'Insufficient permissions to activate playbooks.', 'lolacore' ) );
        }

        $slug = isset($params['playbook_slug']) ? sanitize_title((string) $params['playbook_slug']) : '';
        if ($slug === '') {
            throw new \Exception( __( 'playbook_slug is required.', 'lolacore' ) );
        }

        $row = $this->requireDbRow($slug);
        $id  = (int) ($row['id'] ?? 0);
        $status = (string) ($row['status'] ?? '');

        if ($status === Playbook::STATUS_ACTIVE) {
            return [
                'success'       => true,
                'playbook_slug' => $slug,
                /* translators: 1: value */
                'message'       => sprintf( __( 'Playbook "%s" is already active.', 'lolacore' ), $slug ),
            ];
        }

        if ($status === Playbook::STATUS_ARCHIVED) {
            /* translators: 1: value */
            throw new \Exception( sprintf( __( 'Playbook "%s" is archived and cannot be activated. Restore it from the admin panel first.', 'lolacore' ), esc_html( $slug ) ) );
        }

        if ($dryRun) {
            return [
                'preview'       => true,
                'playbook_slug' => $slug,
                /* translators: 1: value, 2: value */
                'message'       => sprintf( __( 'Will activate playbook "%1$s" (currently %2$s). After activation it will be delivered when matching abilities run or trigger keywords match.', 'lolacore' ), $slug, $status ),
            ];
        }

        $this->buildStore()->setStatus($id, Playbook::STATUS_ACTIVE);

        $logger = new ActionLogger();
        $logger->log(
            'activate_playbook',
            sprintf('Playbook "%s" activated (id: %d).', $slug, $id),
            'success',
            'chat',
            ['playbook_slug' => $slug, 'id' => $id]
        );

        return [
            'success'       => true,
            'playbook_slug' => $slug,
            'id'            => $id,
            /* translators: 1: value */
            'message'       => sprintf( __( 'Playbook "%s" activated.', 'lolacore' ), $slug ),
        ];
    }

    private function deactivatePlaybook(array $params, bool $dryRun): array
    {
        if (!current_user_can('manage_options')) {
            throw new \Exception( __( 'Insufficient permissions to deactivate playbooks.', 'lolacore' ) );
        }

        $slug = isset($params['playbook_slug']) ? sanitize_title((string) $params['playbook_slug']) : '';
        if ($slug === '') {
            throw new \Exception( __( 'playbook_slug is required.', 'lolacore' ) );
        }

        $row = $this->requireDbRow($slug);
        $id  = (int) ($row['id'] ?? 0);
        $status = (string) ($row['status'] ?? '');

        if ($status !== Playbook::STATUS_ACTIVE) {
            return [
                'success'       => true,
                'playbook_slug' => $slug,
                'status'        => $status,
                /* translators: 1: value, 2: value */
                'message'       => sprintf( __( 'Playbook "%1$s" is not active (current status: %2$s). Nothing to deactivate.', 'lolacore' ), $slug, $status ),
            ];
        }

        if ($dryRun) {
            return [
                'preview'       => true,
                'playbook_slug' => $slug,
                /* translators: 1: value */
                'message'       => sprintf( __( 'Will move playbook "%s" from active to inactive. It will stop being delivered but stay in the panel; you can reactivate it later.', 'lolacore' ), $slug ),
            ];
        }

        $this->buildStore()->setStatus($id, Playbook::STATUS_INACTIVE);

        $logger = new ActionLogger();
        $logger->log(
            'deactivate_playbook',
            sprintf('Playbook "%s" deactivated (id: %d, active → inactive).', $slug, $id),
            'success',
            'chat',
            ['playbook_slug' => $slug, 'id' => $id]
        );

        return [
            'success'       => true,
            'playbook_slug' => $slug,
            'status'        => Playbook::STATUS_INACTIVE,
            /* translators: 1: value */
            'message'       => sprintf( __( 'Playbook "%s" deactivated. Now inactive.', 'lolacore' ), $slug ),
        ];
    }

    /**
     * @return array<string,mixed>
     */
    private function requireDbRow(string $slug): array
    {
        $row = $this->buildStore()->getRowBySlug($slug);
        if ($row === null) {
            throw new \Exception(
                sprintf(
                    /* translators: 1: value */
                    __( 'No database playbook found with slug "%s". Built-in, addon, and filesystem playbooks cannot be changed from chat.', 'lolacore' ),
                    esc_html( $slug )
                )
            );
        }

        return $row;
    }

    private function buildLoader(): PlaybookLoader
    {
        return new PlaybookLoader(new YamlParser(), new PlaybookValidator());
    }

    private function buildStore(): PlaybookStore
    {
        return new PlaybookStore($this->buildLoader());
    }

    private function buildRegistry(): PlaybookRegistry
    {
        $registry = new PlaybookRegistry($this->buildLoader());
        $registry->build();
        return $registry;
    }
}

