<?php

/**
 * Alt-text generation via the native WordPress 7 AI client.
 *
 * @package BulkAltText
 */

declare(strict_types=1);

namespace BulkAltText\Ai;

use Throwable;
use WP_Error;

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

/**
 * Builds the alt-text prompt and delegates generation to the WP AI connector.
 *
 * A single {@see AiService::generate()} entry point centralises the AiClient
 * availability check and exception handling, so callers always get either a
 * clean string or a WP_Error.
 */
class AiService
{
    private const AI_CLIENT = '\\WordPress\\AiClient\\AiClient';

    /**
     * Whether an AI connector is available to fulfil requests.
     */
    public static function is_available(): bool
    {
        if (!class_exists(self::AI_CLIENT)) {
            return false;
        }

        try {
            $client = self::AI_CLIENT;
            if (method_exists($client, 'defaultRegistry')) {
                $registry = $client::defaultRegistry();
                if (is_object($registry) && method_exists($registry, 'getRegisteredProviderIds')) {
                    return count($registry->getRegisteredProviderIds()) > 0;
                }
            }
        } catch (Throwable $e) {
            return true; // Client exists; assume usable and let generation surface errors.
        }

        return true;
    }

    /**
     * Generate descriptive alt text for an image using whatever context exists.
     *
     * @param array{filename?:string,title?:string,caption?:string,context?:string} $ctx
     * @param array{language?:string,max_length?:int}                                $opts
     * @return string|WP_Error
     */
    public function alt_text(array $ctx, array $opts = [])
    {
        $maxLen   = isset($opts['max_length']) ? max(60, min(160, (int) $opts['max_length'])) : 125;
        $language = $this->language_instruction($opts['language'] ?? 'site');

        $filename = $ctx['filename'] ?? '';
        $title    = $ctx['title'] ?? '';
        $caption  = $ctx['caption'] ?? '';
        $context  = $this->trim_content($ctx['context'] ?? '', 600);

        $hints = array_filter([
            $filename ? "File name: {$filename}" : '',
            $title ? "Image title: {$title}" : '',
            $caption ? "Caption: {$caption}" : '',
            $context ? "Surrounding page content: {$context}" : '',
        ]);

        if (empty($hints)) {
            $hints[] = 'No metadata is available; infer the likely subject from the file name only.';
        }

        $prompt = "You write concise, descriptive alt text for website images to improve " .
            "accessibility (WCAG) and SEO.\nRules:\n" .
            "- One sentence, under {$maxLen} characters.\n" .
            "- Describe the likely subject factually; do NOT start with \"Image of\", \"Picture of\" or \"Photo of\".\n" .
            "- Do not add quotes, labels, markdown or a trailing period-only fragment.\n" .
            $language .
            "- Output ONLY the alt text, nothing else.\n\n" .
            "Hints:\n" . implode("\n", $hints);

        $out = $this->generate($prompt);
        if (is_wp_error($out)) {
            return $out;
        }

        return $this->clamp_sentence($out, $maxLen);
    }

    /**
     * Core generation call. Returns trimmed plain text or a WP_Error.
     *
     * @return string|WP_Error
     */
    public function generate(string $prompt)
    {
        if (!class_exists(self::AI_CLIENT)) {
            return new WP_Error(
                'batai_no_client',
                __('No AI connector is available. Install and configure an AI provider under Settings → AI in WordPress.', 'bulk-alt-text-ai')
            );
        }

        try {
            $client = self::AI_CLIENT;
            $result = $client::generateTextResult($prompt);
            $text   = method_exists($result, 'toText') ? $result->toText() : (string) $result;
            $text   = $this->sanitize_output($text);

            if ($text === '') {
                return new WP_Error('batai_empty', __('The AI returned an empty response. Try again.', 'bulk-alt-text-ai'));
            }

            return $text;
        } catch (Throwable $e) {
            return new WP_Error(
                'batai_generation_failed',
                sprintf(
                    /* translators: %s: error detail from the AI provider. */
                    __('AI generation failed: %s', 'bulk-alt-text-ai'),
                    $e->getMessage()
                )
            );
        }
    }

    private function language_instruction(string $language): string
    {
        if ($language === 'site') {
            $locale = function_exists('get_locale') ? get_locale() : 'en_US';
            return "- Write the alt text in the language matching locale \"{$locale}\".\n";
        }

        if ($language !== '' && $language !== 'auto') {
            return "- Write the alt text in {$language}.\n";
        }

        return ''; // auto: let the model match the context language.
    }

    private function sanitize_output(string $text): string
    {
        $text = trim($text);
        $text = trim($text, "\"'`");
        $text = (string) preg_replace('/^(alt text|alt|description|caption)\s*[:\-]\s*/i', '', $text);
        $text = (string) preg_replace('/\s+/u', ' ', $text);

        return trim($text);
    }

    private function clamp_sentence(string $text, int $maxChars): string
    {
        $len = function_exists('mb_strlen') ? mb_strlen($text) : strlen($text);
        if ($len <= $maxChars) {
            return $text;
        }

        $cut       = function_exists('mb_substr') ? mb_substr($text, 0, $maxChars) : substr($text, 0, $maxChars);
        $lastSpace = strrpos($cut, ' ');
        if ($lastSpace !== false && $lastSpace > $maxChars - 25) {
            $cut = substr($cut, 0, $lastSpace);
        }

        return rtrim($cut, " ,.;:") . '…';
    }

    private function trim_content(string $content, int $maxChars): string
    {
        $content = wp_strip_all_tags($content);
        $content = trim((string) preg_replace('/\s+/u', ' ', $content));

        $len = function_exists('mb_strlen') ? mb_strlen($content) : strlen($content);
        if ($len <= $maxChars) {
            return $content;
        }

        return function_exists('mb_substr') ? mb_substr($content, 0, $maxChars) : substr($content, 0, $maxChars);
    }
}
