<?php

declare(strict_types=1);

namespace PrettyLinks\Pro\Rest;

use PrettyLinks\Pro\Replacements\CacheInvalidator;
use PrettyLinks\Pro\Replacements\KeywordRepository;
use PrettyLinks\Rest\Controllers\BaseController;
use WP_REST_Request;
use WP_REST_Response;
use WP_REST_Server;

/**
 * Pro keywords CRUD at `/pretty-links/v1/keywords`.
 */
class KeywordsController extends BaseController
{
    /**
     * Register the keywords REST routes.
     *
     * @return void
     */
    public function register(): void
    {
        register_rest_route($this->namespace(), '/keywords', [
            [
                'methods'             => WP_REST_Server::READABLE,
                'callback'            => [$this, 'index'],
                'permission_callback' => $this->permission(),
            ],
            [
                'methods'             => WP_REST_Server::CREATABLE,
                'callback'            => [$this, 'create'],
                'permission_callback' => $this->permission(),
            ],
        ]);

        register_rest_route($this->namespace(), '/keywords/(?P<id>\d+)', [
            [
                'methods'             => WP_REST_Server::EDITABLE,
                'callback'            => [$this, 'update'],
                'permission_callback' => $this->permission(),
            ],
            [
                'methods'             => WP_REST_Server::DELETABLE,
                'callback'            => [$this, 'delete'],
                'permission_callback' => $this->permission(),
            ],
        ]);
    }

    /**
     * Return all keywords.
     *
     * @return WP_REST_Response
     */
    public function index(): WP_REST_Response
    {
        return new WP_REST_Response($this->repo()->all());
    }

    /**
     * Create a keyword from the request body.
     *
     * @param WP_REST_Request $request Incoming REST request.
     *
     * @return WP_REST_Response
     */
    public function create(WP_REST_Request $request): WP_REST_Response
    {
        $data = (array) $request->get_json_params();
        $row  = $this->repo()->create($data);
        if (!isset($row['error'])) {
            // Log the add so existing posts scan for it on next render.
            $invalidator = $this->invalidator();
            if ($invalidator !== null) {
                $invalidator->onKeywordAdded(
                    (int) $row['id'],
                    (string) $row['keyword']
                );
            }
        }
        $status = isset($row['error']) ? 422 : 201;
        return new WP_REST_Response($row, $status);
    }

    /**
     * Update a keyword by id.
     *
     * @param WP_REST_Request $request Incoming REST request.
     *
     * @return WP_REST_Response
     */
    public function update(WP_REST_Request $request): WP_REST_Response
    {
        $id       = (int) $request['id'];
        $data     = (array) $request->get_json_params();
        $existing = $this->repo()->find($id);
        if ($existing === null) {
            return new WP_REST_Response(['error' => 'not_found'], 404);
        }
        $row = $this->repo()->update($id, $data);
        if ($row === null) {
            return new WP_REST_Response(['error' => 'not_found'], 404);
        }

        $invalidator = $this->invalidator();
        if ($invalidator === null) {
            return new WP_REST_Response($row);
        }

        if (($existing['keyword'] ?? null) !== ($row['keyword'] ?? null)) {
            // Text changed — the UI can't do this, but third-party callers
            // can. Existing index rows for this keyword_id are semantically
            // stale (they say "post contains old text") so we wipe them
            // surgically, then log the new text as an add.
            $this->repo()->clearKeywordFromIndex($id);
            $invalidator->onKeywordAdded(
                $id,
                (string) $row['keyword']
            );
        } else {
            // Flag-only change; index mapping unaffected. Just clear the
            // list-level cache since joined data may have changed.
            $invalidator->onLinkMutated();
        }
        return new WP_REST_Response($row);
    }

    /**
     * Delete a keyword by id.
     *
     * @param WP_REST_Request $request Incoming REST request.
     *
     * @return WP_REST_Response
     */
    public function delete(WP_REST_Request $request): WP_REST_Response
    {
        $id = (int) $request['id'];
        // Repository::delete removes prli_post_keywords rows for this
        // keyword_id. No index-wide wipe; other posts stay correctly
        // indexed for every other keyword.
        $ok          = $this->repo()->delete($id);
        $invalidator = $this->invalidator();
        if ($invalidator !== null) {
            $invalidator->onKeywordRemoved();
        }
        return new WP_REST_Response(['success' => $ok]);
    }

    /**
     * Resolve the keyword repository from the container.
     *
     * @return KeywordRepository
     */
    private function repo(): KeywordRepository
    {
        return $this->container->get(KeywordRepository::class);
    }

    /**
     * Resolve the cache invalidator from the container, if available.
     *
     * @return CacheInvalidator|null
     */
    private function invalidator(): ?CacheInvalidator
    {
        if (!$this->container->has(CacheInvalidator::class)) {
            return null;
        }
        return $this->container->get(CacheInvalidator::class);
    }
}
