<?php
/**
 * Template reads and runtime resolution via Bricks Database.
 *
 * @package LolaCoreForBricks
 */

declare(strict_types=1);

namespace LolaCoreForBricks\Gateways;

use LolaCoreForBricks\Elements\ElementTree;
use LolaCoreForBricks\Elements\IdGenerator;
use LolaCoreForBricks\Support\BricksMetaKeys;
use LolaCoreForBricks\Support\PaginatedList;

/**
 * Reads bricks_template posts and resolves active templates for a page.
 */
class TemplateGateway
{
    private const TEMPLATE_POST_TYPE = 'bricks_template';

    /**
     * @var string[]
     */
    public const TYPES = [
        'header',
        'footer',
        'content',
        'section',
        'archive',
        'search',
        'error',
        'popup',
        'password_protection',
    ];

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function getTemplates(array $params): array
    {
        $type   = isset($params['type']) ? (string) $params['type'] : '';
        $limit  = (int) ($params['limit'] ?? 50);
        $offset = (int) ($params['offset'] ?? 0);

        $templates = $this->loadTemplates($type);
        $page      = PaginatedList::slice($templates, $limit, $offset);

        return [
            'templates' => $page['items'],
            'total'     => $page['total'],
            'truncated' => $page['truncated'],
            'limit'     => $page['limit'],
            'offset'    => $page['offset'],
        ];
    }

    /**
     * @param array<string, mixed> $params
     * @return array<string, mixed>
     */
    public function resolveTemplateForPage(array $params): array
    {
        $postId = (int) ($params['post_id'] ?? 0);
        if ($postId <= 0) {
            return ['error' => 'missing_post_id'];
        }

        $post = get_post($postId);
        if (!$post instanceof \WP_Post) {
            return ['error' => 'post_not_found', 'post_id' => $postId];
        }

        if (!class_exists(\Bricks\Database::class) || !class_exists(\Bricks\Helpers::class)) {
            return ['error' => 'bricks_resolver_unavailable'];
        }

        $this->primePostContext($post);

        \Bricks\Database::$active_templates = [];
        \Bricks\Database::set_active_templates($postId);

        $active       = is_array(\Bricks\Database::$active_templates) ? \Bricks\Database::$active_templates : [];
        $contentType  = (string) ($active['content_type'] ?? 'content');
        $templateIds  = \Bricks\Database::get_all_templates_by_type();
        $previewType  = '';

        $areas = [];
        foreach (['header', 'footer', 'content'] as $part) {
            $winnerId = (int) ($active[$part] ?? 0);
            $areas[$part] = $this->buildAreaResolution(
                $part,
                $winnerId,
                $templateIds,
                $contentType,
                $postId,
                $previewType
            );
        }

        wp_reset_postdata();

        return [
            'post_id'      => $postId,
            'post_title'   => (string) get_the_title($postId),
            'content_type' => $contentType,
            'areas'        => $areas,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function createTemplate(string $title, string $type, string $status = 'publish'): array
    {
        $type = $this->normalizeType($type);
        if ($type === '') {
            return $this->failure('I need the template type. Header, footer, content, or archive?', 'missing_type');
        }

        $title = trim($title);
        if ($title === '') {
            return $this->failure('I need a name for the template.', 'missing_title');
        }

        $templateId = (int) wp_insert_post([
            'post_title'  => $title,
            'post_status' => $status === 'draft' ? 'draft' : 'publish',
            'post_type'   => self::TEMPLATE_POST_TYPE,
        ]);

        if ($templateId <= 0) {
            return $this->failure('I could not create that template. Try again.', 'create_failed');
        }

        $this->writeType($templateId, $type);

        return [
            'success'     => true,
            'template_id' => $templateId,
            'title'       => $title,
            'type'        => $type,
            'status'      => $status === 'draft' ? 'draft' : 'publish',
        ];
    }

    /**
     * @param array<int, mixed> $conditions
     * @return array<string, mixed>
     */
    public function writeConditions(int $templateId, array $conditions): array
    {
        if (!$this->isTemplate($templateId)) {
            return $this->failure('That is not a Bricks template.', 'not_a_template');
        }

        $settings = $this->templateSettings($templateId);
        $settings['templateConditions'] = array_values($conditions);
        $this->writeSettings($templateId, $settings);

        return [
            'success'     => true,
            'template_id' => $templateId,
            'conditions'  => $settings['templateConditions'],
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function duplicateTemplate(int $templateId, ElementGateway $elements, IdGenerator $ids): array
    {
        if (!$this->isTemplate($templateId)) {
            return $this->failure('That is not a Bricks template.', 'not_a_template');
        }

        $title  = 'Copy of ' . (string) get_the_title($templateId);
        $type   = $this->templateType($templateId);
        $created = $this->createTemplate($title, $type !== '' ? $type : 'content', 'publish');
        if (($created['success'] ?? false) !== true) {
            return $created;
        }

        $newId = (int) $created['template_id'];
        $this->writeSettings($newId, $this->templateSettings($templateId));

        $idMaps = [];
        foreach ([BricksMetaKeys::AREA_CONTENT, BricksMetaKeys::AREA_HEADER, BricksMetaKeys::AREA_FOOTER] as $area) {
            $tree = $elements->read($templateId, $area);
            if ($tree === []) {
                continue;
            }

            $remapped = ElementTree::fromList($tree)->withRemappedIds($ids);
            $written  = $elements->write($newId, $area, $remapped, false);
            if (($written['success'] ?? false) !== true) {
                return $written;
            }

            $idMaps[$area] = array_combine(
                array_map(static fn (array $el): string => (string) ($el['id'] ?? ''), $tree),
                array_map(static fn (array $el): string => (string) ($el['id'] ?? ''), $remapped)
            ) ?: [];
        }

        return [
            'success'     => true,
            'template_id' => $newId,
            'source_id'   => $templateId,
            'title'       => $title,
            'type'        => $type,
            'id_maps'     => $idMaps,
        ];
    }

    /**
     * @return array<string, mixed>
     */
    public function trashTemplate(int $templateId): array
    {
        if (!$this->isTemplate($templateId)) {
            return $this->failure('That is not a Bricks template.', 'not_a_template');
        }

        $trashed = wp_trash_post($templateId);
        if ($trashed === false || $trashed === null) {
            return $this->failure('I could not move that template to the trash.', 'trash_failed');
        }

        return [
            'success'     => true,
            'template_id' => $templateId,
            'status'      => 'trash',
        ];
    }

    public function isTemplate(int $templateId): bool
    {
        if ($templateId <= 0 || get_post($templateId) === null) {
            return false;
        }

        $type = function_exists('get_post_type') ? (string) get_post_type($templateId) : '';

        return $type === self::TEMPLATE_POST_TYPE;
    }

    public function defaultAreaForType(string $type): string
    {
        return match ($this->normalizeType($type)) {
            'header' => BricksMetaKeys::AREA_HEADER,
            'footer' => BricksMetaKeys::AREA_FOOTER,
            default  => BricksMetaKeys::AREA_CONTENT,
        };
    }

    public function normalizeType(string $type): string
    {
        $type = sanitize_key($type);
        if ($type === '') {
            return '';
        }

        if (in_array($type, self::TYPES, true) || str_starts_with($type, 'wc_')) {
            return $type;
        }

        return $type;
    }

    /**
     * @return array<int, mixed>
     */
    public function readConditions(int $templateId): array
    {
        return $this->templateConditions($templateId);
    }

    public function readType(int $templateId): string
    {
        return $this->templateType($templateId);
    }

    /**
     * @return array<int, array<string, mixed>>
     */
    private function loadTemplates(string $typeFilter): array
    {
        $query = new \WP_Query([
            'post_type'      => self::TEMPLATE_POST_TYPE,
            'post_status'    => ['publish', 'draft', 'private'],
            'posts_per_page' => -1,
            'orderby'        => 'title',
            'order'          => 'ASC',
            'no_found_rows'  => true,
        ]);

        $items = [];
        foreach ($query->posts as $post) {
            if (!$post instanceof \WP_Post) {
                continue;
            }

            $templateType = $this->templateType($post->ID);
            if ($typeFilter !== '' && $templateType !== $typeFilter) {
                continue;
            }

            $settings   = $this->templateSettings($post->ID);
            $conditions = is_array($settings['templateConditions'] ?? null) ? $settings['templateConditions'] : [];

            $items[] = [
                'id'                 => $post->ID,
                'title'              => (string) get_the_title($post),
                'status'             => (string) $post->post_status,
                'type'               => $templateType,
                'conditions'         => $conditions,
                'conditions_summary' => $this->summarizeConditions($conditions),
            ];
        }

        wp_reset_postdata();

        return $items;
    }

    /**
     * @param array<string, mixed> $templateIds
     * @return array<string, mixed>
     */
    private function buildAreaResolution(
        string $part,
        int $winnerId,
        array $templateIds,
        string $contentType,
        int $postId,
        string $previewType
    ): array {
        $loopType = $part === 'content' ? 'body' : $part;
        $candidates = is_array($templateIds[$loopType] ?? null) ? $templateIds[$loopType] : [];

        $evaluated = [];
        foreach ($candidates as $templateId) {
            $templateId = (int) $templateId;
            $conditions = $this->templateConditions($templateId);
            $scores     = \Bricks\Database::screen_conditions([], $templateId, $conditions, $postId, $previewType);
            $score      = is_array($scores) && $scores !== [] ? max(array_keys($scores)) : 0;

            $evaluated[] = [
                'template_id'    => $templateId,
                'title'          => (string) get_the_title($templateId),
                'type'           => $this->templateType($templateId),
                'score'          => $score,
                'matched'        => $score > 0 || ($conditions === [] && !$this->defaultsDisabled()),
                'conditions'     => $conditions,
                'plain_language' => $this->summarizeConditions($conditions),
            ];
        }

        usort(
            $evaluated,
            static fn (array $a, array $b): int => ($b['score'] <=> $a['score']) ?: ($b['template_id'] <=> $a['template_id'])
        );

        $winner = null;
        $losers = [];
        foreach ($evaluated as $row) {
            if ($row['template_id'] === $winnerId) {
                $winner = $row;
                continue;
            }

            if ($row['matched'] || $row['score'] > 0) {
                $losers[] = [
                    'template_id'    => $row['template_id'],
                    'title'          => $row['title'],
                    'score'          => $row['score'],
                    'plain_language' => $row['plain_language'],
                    'reason_lost'    => $winner !== null
                        ? 'Lower priority than the winning template.'
                        : 'Did not match this page context.',
                ];
            }
        }

        if ($winnerId > 0 && $winner === null) {
            $winner = [
                'template_id'    => $winnerId,
                'title'          => (string) get_the_title($winnerId),
                'type'           => $this->templateType($winnerId),
                'score'          => null,
                'matched'        => true,
                'conditions'     => $this->templateConditions($winnerId),
                'plain_language' => $this->summarizeConditions($this->templateConditions($winnerId)),
            ];
        }

        return [
            'winner'  => $winner,
            'losers'  => array_slice($losers, 0, 10),
            'part'    => $part,
            'content_type_context' => $contentType,
        ];
    }

    private function primePostContext(\WP_Post $post): void
    {
        global $wp_query, $post;

        $wp_query = new \WP_Query([
            'p'              => $post->ID,
            'post_type'      => $post->post_type,
            'posts_per_page' => 1,
        ]);

        if ($wp_query->have_posts()) {
            $wp_query->the_post();
        } else {
            $post = $post;
            setup_postdata($post);
        }
    }

    /**
     * @return array<string, mixed>
     */
    private function templateSettings(int $templateId): array
    {
        $settings = get_post_meta($templateId, BricksMetaKeys::TEMPLATE_SETTINGS, true);

        return is_array($settings) ? $settings : [];
    }

    /**
     * @return array<int, mixed>
     */
    private function templateConditions(int $templateId): array
    {
        $settings = $this->templateSettings($templateId);
        $conditions = $settings['templateConditions'] ?? [];

        return is_array($conditions) ? $conditions : [];
    }

    private function templateType(int $templateId): string
    {
        if (class_exists(\Bricks\Templates::class)) {
            return (string) \Bricks\Templates::get_template_type($templateId);
        }

        $type = get_post_meta($templateId, $this->typeMetaKey(), true);

        return is_string($type) && $type !== '' ? $type : 'content';
    }

    /**
     * @param array<string, mixed> $settings
     */
    private function writeSettings(int $templateId, array $settings): void
    {
        if (class_exists(\Bricks\Helpers::class)) {
            \Bricks\Helpers::set_template_settings($templateId, $settings);

            return;
        }

        update_post_meta($templateId, BricksMetaKeys::TEMPLATE_SETTINGS, $settings);
    }

    private function writeType(int $templateId, string $type): void
    {
        update_post_meta($templateId, $this->typeMetaKey(), $type);
    }

    private function typeMetaKey(): string
    {
        return defined('BRICKS_DB_TEMPLATE_TYPE')
            ? (string) BRICKS_DB_TEMPLATE_TYPE
            : BricksMetaKeys::TEMPLATE_TYPE;
    }

    /**
     * @return array{success: false, message: string, error: string}
     */
    private function failure(string $message, string $error): array
    {
        return [
            'success' => false,
            'message' => $message,
            'error'   => $error,
        ];
    }

    /**
     * @param array<int, mixed> $conditions
     */
    private function summarizeConditions(array $conditions): string
    {
        if ($conditions === []) {
            return 'Default template (no display conditions).';
        }

        $parts = [];
        foreach ($conditions as $condition) {
            if (!is_array($condition)) {
                continue;
            }

            $main    = (string) ($condition['main'] ?? '');
            $exclude = !empty($condition['exclude']) ? 'exclude ' : '';

            $parts[] = match ($main) {
                'any'      => $exclude . 'entire website',
                'ids'      => $exclude . 'specific pages/posts',
                'terms'    => $exclude . 'taxonomy terms',
                'postType' => $exclude . 'post types: ' . implode(', ', (array) ($condition['postType'] ?? [])),
                'archiveType' => $exclude . 'archive context',
                default    => $exclude . $main,
            };
        }

        return $parts === [] ? 'Conditional template' : implode('; ', $parts);
    }

    private function defaultsDisabled(): bool
    {
        if (!class_exists(\Bricks\Database::class)) {
            return false;
        }

        return (bool) \Bricks\Database::get_setting('defaultTemplatesDisabled', false);
    }
}
