<?php

declare(strict_types=1);

namespace Instinctor\Services;

use Instinctor\Core\Auth;
use Instinctor\Core\DB;


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

/**
 * LibraryService — per-user local library for sections and scenes.
 *
 * Two separate storage surfaces:
 *   - instinctor_library_sections : reusable blocks (inside pages OR screens)
 *   - instinctor_library_scenes   : whole scene-mode shells (background + scene root + screens)
 *
 * Scope is always per-user (created_by = Auth::userId()). Designers' items
 * follow them across every page they build on this install.
 *
 * Schema deliberately mirrors Platform's {prefix}sections table on
 * platform.instinctor.com (see platform_v2/migrations/0003_library.php) so an
 * admin can promote a local item to the global library by copying columns 1:1.
 *
 * @since 2.1.159
 */
class LibraryService
{
    private const KIND_SECTION = 'section';
    private const KIND_SCENE   = 'scene';

    // ── Table resolution ──────────────────────────────────────────────────────

    private static function tableFor(string $kind): string
    {
        $base = ($kind === self::KIND_SCENE) ? 'instinctor_library_scenes' : 'instinctor_library_sections';
        return DB::prefix() . $base;
    }

    // ── Reads ─────────────────────────────────────────────────────────────────

    /**
     * List current user's saved items of the given kind ('section' | 'scene').
     * Returns rows with element_tree (and page_settings for scenes) decoded.
     */
    public static function listMine(string $kind): array
    {
        $userId = Auth::userId();
        if (!$userId) return [];

        $t = self::tableFor($kind);
        $cols = ($kind === self::KIND_SCENE)
            ? 'id, name, category, tags, element_tree, html_preview, page_settings, sort_order, created_at, updated_at'
            : 'id, name, category, tags, element_tree, html_preview, sort_order, created_at, updated_at';

        $rows = DB::get_results(DB::prepare(
            "SELECT {$cols} FROM {$t}
             WHERE created_by = %d
             ORDER BY sort_order ASC, id DESC",
            $userId
        ), ARRAY_A) ?: [];

        foreach ($rows as &$r) {
            $r['id']           = (int) $r['id'];
            $r['element_tree'] = json_decode((string) $r['element_tree'], true) ?: [];
            $r['tags']         = $r['tags'] ? array_map('trim', explode(',', (string) $r['tags'])) : [];
            if ($kind === self::KIND_SCENE) {
                $r['page_settings'] = json_decode((string) $r['page_settings'], true) ?: [];
            }
        }
        unset($r);

        return $rows;
    }

    /** Fetch a single item, owner-scoped. Returns null if not found or not owned. */
    public static function getMine(string $kind, int $id): ?array
    {
        $userId = Auth::userId();
        if (!$userId || !$id) return null;

        $t = self::tableFor($kind);
        $cols = ($kind === self::KIND_SCENE)
            ? 'id, name, category, tags, element_tree, html_preview, page_settings, sort_order, created_at, updated_at'
            : 'id, name, category, tags, element_tree, html_preview, sort_order, created_at, updated_at';

        $row = DB::get_row(DB::prepare(
            "SELECT {$cols} FROM {$t} WHERE id = %d AND created_by = %d",
            $id, $userId
        ), ARRAY_A);

        if (!$row) return null;

        $row['id']           = (int) $row['id'];
        $row['element_tree'] = json_decode((string) $row['element_tree'], true) ?: [];
        $row['tags']         = $row['tags'] ? array_map('trim', explode(',', (string) $row['tags'])) : [];
        if ($kind === self::KIND_SCENE) {
            $row['page_settings'] = json_decode((string) $row['page_settings'], true) ?: [];
        }

        return $row;
    }

    // ── Writes ────────────────────────────────────────────────────────────────

    /**
     * Save a new section for the current user.
     *
     * @param array  $elementTree Flat array of elements (root + descendants), same shape as PageService
     * @param string $htmlPreview Rendered HTML snapshot of the section (for palette tile rendering)
     * @return int|null Inserted ID, or null on failure / not logged in
     */
    public static function saveSection(
        string $name,
        array  $elementTree,
        string $htmlPreview,
        string $category = 'other',
        array  $tags     = []
    ): ?int {
        return self::saveRow(self::KIND_SECTION, [
            'name'         => $name,
            'category'     => $category,
            'tags'         => implode(',', array_map('trim', $tags)),
            'element_tree' => wp_json_encode($elementTree) ?: '[]',
            'html_preview' => $htmlPreview,
        ]);
    }

    /**
     * Save a new scene for the current user.
     *
     * @param array  $pageSettings Scene-mode page settings (scene_mode, bg_type, bg effects, etc.)
     */
    public static function saveScene(
        string $name,
        array  $elementTree,
        array  $pageSettings,
        string $htmlPreview,
        string $category = 'other',
        array  $tags     = []
    ): ?int {
        return self::saveRow(self::KIND_SCENE, [
            'name'          => $name,
            'category'      => $category,
            'tags'          => implode(',', array_map('trim', $tags)),
            'element_tree'  => wp_json_encode($elementTree) ?: '[]',
            'page_settings' => wp_json_encode($pageSettings) ?: '{}',
            'html_preview'  => $htmlPreview,
        ]);
    }

    private static function saveRow(string $kind, array $data): ?int
    {
        $userId = Auth::userId();
        if (!$userId) return null;

        $t   = self::tableFor($kind);
        $now = current_time('mysql');

        $data['created_by'] = $userId;
        $data['created_at'] = $now;
        $data['updated_at'] = $now;

        $ok = DB::insert($t, $data);
        if (!$ok) return null;

        return (int) DB::insert_id();
    }

    /**
     * Rename or recategorize an owned item. Ignores unknown fields.
     * @param array{name?:string,category?:string,tags?:array<string>,sort_order?:int} $fields
     */
    public static function update(string $kind, int $id, array $fields): bool
    {
        $userId = Auth::userId();
        if (!$userId || !$id) return false;

        $allowed = [];
        if (isset($fields['name']))       $allowed['name']       = (string) $fields['name'];
        if (isset($fields['category']))   $allowed['category']   = (string) $fields['category'];
        if (isset($fields['tags']))       $allowed['tags']       = implode(',', array_map('trim', (array) $fields['tags']));
        if (isset($fields['sort_order'])) $allowed['sort_order'] = (int) $fields['sort_order'];
        if (!$allowed) return false;

        $allowed['updated_at'] = current_time('mysql');

        $t      = self::tableFor($kind);
        $result = DB::update($t, $allowed, ['id' => $id, 'created_by' => $userId]);
        return $result !== false;
    }

    /** Delete an owned item. Returns false if not owned or not found. */
    public static function delete(string $kind, int $id): bool
    {
        $userId = Auth::userId();
        if (!$userId || !$id) return false;

        $t      = self::tableFor($kind);
        $result = DB::delete($t, ['id' => $id, 'created_by' => $userId]);
        return $result !== false && $result > 0;
    }

    // ── Platform global library (read-only for all users) ────────────────────

    private const PLATFORM_CDN_URL = 'https://platform.instinctor.com/api/sections';
    private const PLATFORM_CACHE_TTL = 300; // 5 minutes

    /**
     * Fetch curated items from the Platform global library.
     *
     * Plugin does a server-side fetch (not CORS-from-browser) so we can
     * transient-cache the response across all editor sessions on the install.
     * Platform returns published items only; empty array on any error so the
     * UI degrades to "My Library" without a hard failure.
     *
     * @param string $kind 'section' | 'scene'
     */
    public static function fetchPlatformItems(string $kind): array
    {
        $kind = ($kind === self::KIND_SCENE) ? self::KIND_SCENE : self::KIND_SECTION;
        $key  = 'instinctor_lib_platform_' . $kind;

        if (function_exists('get_transient')) {
            $cached = get_transient($key);
            if (is_array($cached)) return $cached;
        }

        $url  = self::PLATFORM_CDN_URL . '?type=' . rawurlencode($kind);
        $body = self::httpGetJson($url);
        $items = (is_array($body) && !empty($body['data'])) ? $body['data'] : [];

        // Normalize shape so frontend code is indifferent to source:
        // always an array with id, name, category, tags, element_tree,
        // html_preview, page_settings (for scenes).
        foreach ($items as &$it) {
            $it['id']            = (int) ($it['id'] ?? 0);
            $it['element_tree']  = is_array($it['element_tree'] ?? null) ? $it['element_tree'] : [];
            $it['page_settings'] = is_array($it['page_settings'] ?? null) ? $it['page_settings'] : [];
            $it['tags']          = is_array($it['tags'] ?? null) ? $it['tags'] : [];
            $it['tier']          = (($it['tier'] ?? '') === 'free') ? 'free' : 'pro';
            $it['source']        = 'platform';
        }
        unset($it);

        if (function_exists('set_transient')) {
            set_transient($key, $items, self::PLATFORM_CACHE_TTL);
        }
        return $items;
    }

    /**
     * Upload a local item to the Platform global library. Admin only — the
     * X-Platform-Token secret must be defined in this install's env. Normal
     * installs won't have it, so this silently no-ops for them at the
     * controller gate.
     *
     * @param string $kind 'section' | 'scene'
     * @param array  $data Must include name + element_tree (+ page_settings for scenes)
     * @return array{success:bool,id?:int,error?:string}
     */
    public static function uploadToPlatform(string $kind, array $data): array
    {
        $token = defined('INSTINCTOR_PLATFORM_TOKEN') ? INSTINCTOR_PLATFORM_TOKEN : '';
        if ($token === '') return ['success' => false, 'error' => 'Platform token not configured'];

        $kind = ($kind === self::KIND_SCENE) ? self::KIND_SCENE : self::KIND_SECTION;

        // html_preview for scenes is a thumbnail URL; for sections it's raw HTML.
        // Scene URLs are stored relative ("/storage/...") so they resolve on
        // instinctor.com. Once they live on Platform and are rendered from
        // platform.instinctor.com, the relative path 404s. Upgrade to absolute
        // with this install's home URL so the asset resolves from anywhere.
        $htmlPreview = (string) ($data['html_preview'] ?? '');
        if ($htmlPreview !== '' && str_starts_with($htmlPreview, '/')) {
            $origin = function_exists('home_url') ? rtrim((string) home_url('/'), '/') : '';
            if ($origin !== '') $htmlPreview = $origin . $htmlPreview;
        }

        $payload = [
            'name'          => (string) ($data['name']         ?? ''),
            'type'          => $kind,
            'category'      => (string) ($data['category']     ?? 'other'),
            'tags'          => is_array($data['tags'] ?? null) ? $data['tags'] : [],
            'element_tree'  => $data['element_tree']  ?? [],
            'html_preview'  => $htmlPreview,
            'page_settings' => is_array($data['page_settings'] ?? null) ? $data['page_settings'] : [],
            'status'        => 'published',
        ];

        $url = 'https://platform.instinctor.com/api/admin/library/sections/create';

        if (!function_exists('wp_remote_post')) {
            return ['success' => false, 'error' => 'HTTP client unavailable'];
        }
        // WP.org guideline 7/9 — admin-only upload to platform library is
        // gated on opt-in (sends full element_tree + html preview).
        if (!\Instinctor\Core\PlatformConsent::granted()) {
            return ['success' => false, 'error' => 'platform_consent_required'];
        }
        $resp = wp_remote_post($url, [
            'timeout' => 15,
            'headers' => [
                'Content-Type'      => 'application/json',
                'X-Platform-Token'  => $token,
            ],
            'body' => wp_json_encode($payload),
        ]);

        if (is_wp_error($resp)) {
            return ['success' => false, 'error' => $resp->get_error_message()];
        }
        $code = wp_remote_retrieve_response_code($resp);
        $body = json_decode(wp_remote_retrieve_body($resp), true) ?: [];
        if ($code !== 200 || empty($body['success'])) {
            return ['success' => false, 'error' => $body['error'] ?? ('HTTP ' . $code)];
        }

        // Invalidate transient so the newly uploaded item shows up next load.
        if (function_exists('delete_transient')) {
            delete_transient('instinctor_lib_platform_' . $kind);
        }

        return ['success' => true, 'id' => (int) ($body['id'] ?? 0)];
    }

    /** Lightweight JSON GET helper; returns decoded body or empty array. */
    private static function httpGetJson(string $url): array
    {
        if (!function_exists('wp_remote_get')) return [];
        $resp = wp_remote_get($url, ['timeout' => 10]);
        if (is_wp_error($resp)) return [];
        $code = wp_remote_retrieve_response_code($resp);
        if ($code !== 200) return [];
        $body = wp_remote_retrieve_body($resp);
        $decoded = json_decode($body, true);
        return is_array($decoded) ? $decoded : [];
    }
}
