<?php

declare(strict_types=1);

namespace PpPhp\Plugins;

use PpPhp\Core\EditPlugin;
use PpPhp\Core\PluginSupport;

/**
 * Tags / multi-select input. Type + Enter (or the separator key) to add a tag;
 * click × or Backspace to remove. Values are stored as a separator-joined string
 * in a hidden input, so the field round-trips like any text column. Optionally
 * restricted to a fixed set of options (multi-select), or free-form.
 *
 * The enhancer is emitted inline and self-invoking (per-element guarded), so it
 * works in the Designer live preview and generated apps without any extra wiring.
 * Pair with the "Tags" VIEW control to display saved values as pills.
 */
class TagInput implements EditPlugin
{
    use PluginSupport;

    public static function id(): string { return 'TagInput'; }
    public static function label(): string { return 'Tags / Multi-select'; }
    public static function version(): string { return '1.0.0'; }
    public static function description(): string { return 'Add multiple values as removable tag pills (free-form or from a fixed list).'; }

    public static function render(string $fieldName, mixed $value, array $settings): string
    {
        $name = self::e($fieldName);
        $sep  = (string) ($settings['separator'] ?? ',');
        $sep  = $sep === '' ? ',' : substr($sep, 0, 1);

        $raw  = (string) ($value ?? '');
        $tags = array_values(array_filter(array_map('trim', $raw === '' ? [] : explode($sep, $raw)), fn ($t) => $t !== ''));

        $options  = self::optionList($settings['options'] ?? '');
        $freeform = !in_array(strtolower((string) ($settings['freeform'] ?? 'yes')), ['0', 'no', 'false', 'off'], true);
        $max      = (int) ($settings['max'] ?? 0);
        $ph       = self::e($settings['placeholder'] ?? 'Add…');

        $cfg = self::e(json_encode(['sep' => $sep, 'freeform' => $freeform, 'max' => $max, 'options' => $options], JSON_UNESCAPED_SLASHES | JSON_HEX_APOS | JSON_HEX_QUOT));

        $pills = '';
        foreach ($tags as $t) {
            $pills .= '<span class="pp-tag" data-v="' . self::e($t) . '">' . self::e($t)
                . '<button type="button" class="pp-tag-x" aria-label="Remove ' . self::e($t) . '">&times;</button></span>';
        }

        $listId = '';
        $datalist = '';
        if ($options !== [] && !$freeform) {
            $listId = 'ppdl_' . preg_replace('/[^A-Za-z0-9_]/', '_', $fieldName);
            $datalist = '<datalist id="' . $listId . '">';
            foreach ($options as $o) $datalist .= '<option value="' . self::e($o) . '">';
            $datalist .= '</datalist>';
        }
        $listAttr = $listId !== '' ? ' list="' . $listId . '"' : '';

        $html = '<div class="pp-tags" data-pp-tags="' . $cfg . '">'
            . $pills
            . '<input type="text" class="pp-tag-input" placeholder="' . $ph . '"' . $listAttr . ' autocomplete="off">'
            . '<input type="hidden" class="pp-tag-hidden" name="' . $name . '" value="' . self::e(implode($sep, $tags)) . '">'
            . '</div>' . $datalist;

        return $html . self::enhancer();
    }

    /** @return array<int,string> */
    private static function optionList(mixed $raw): array
    {
        if (is_array($raw)) $items = $raw;
        else $items = preg_split('/\r\n|\r|\n|,/', (string) $raw) ?: [];
        $out = [];
        foreach ($items as $i) { $i = trim((string) $i); if ($i !== '' && !in_array($i, $out, true)) $out[] = $i; }
        return $out;
    }

    public static function settingsSchema(): array
    {
        return [
            ['key' => 'options',     'type' => 'textarea', 'label' => 'Allowed options', 'default' => '', 'hint' => 'One per line (or comma-separated). Leave blank for free-form tags.'],
            ['key' => 'freeform',    'type' => 'bool',     'label' => 'Allow free-form tags', 'default' => 'yes', 'hint' => 'Off = only the options above.'],
            ['key' => 'separator',   'type' => 'text',     'label' => 'Separator', 'default' => ',', 'hint' => 'Character used to join stored values.'],
            ['key' => 'max',         'type' => 'number',   'label' => 'Max tags', 'default' => 0, 'hint' => '0 = no limit.'],
            ['key' => 'placeholder', 'type' => 'text',     'label' => 'Placeholder', 'default' => 'Add…'],
        ];
    }

    public static function assets(): array
    {
        return ['js' => [], 'css' => ['plugins/Edit/TagInput/tag-input.css']];
    }

    /**
     * Inline enhancer: defines window.__ppTagsInit(root) ONCE, but invokes it on
     * every emission (per-element guarded via box._ppTags), so a freshly-injected
     * Designer preview or a server-rendered form both enhance without extra wiring.
     */
    private static function enhancer(): string
    {
        return <<<'HTML'
<script>
(function(){
  if (!window.__ppTagsInit) {
    window.__ppTagsInit = function(root){
      root = root || document;
      root.querySelectorAll('.pp-tags').forEach(function(box){
        if (box._ppTags) return; box._ppTags = true;
        var hidden = box.querySelector('.pp-tag-hidden');
        var input  = box.querySelector('.pp-tag-input');
        if (!hidden || !input) return;
        var cfg = {}; try { cfg = JSON.parse(box.getAttribute('data-pp-tags') || '{}'); } catch (e) {}
        var sep = cfg.sep || ',';
        var opts = Array.isArray(cfg.options) ? cfg.options : [];
        function tags(){ return Array.prototype.map.call(box.querySelectorAll('.pp-tag'), function(p){ return p.getAttribute('data-v'); }); }
        function sync(){ hidden.value = tags().join(sep); }
        function bindX(x){ x.addEventListener('click', function(){ var p = x.closest('.pp-tag'); if (p) { p.remove(); sync(); } }); }
        function addTag(v){
          v = (v || '').trim();
          if (!v || v.indexOf(sep) !== -1) return;
          if (cfg.max && tags().length >= cfg.max) return;
          if (tags().indexOf(v) !== -1) return;
          if (opts.length && !cfg.freeform && opts.indexOf(v) === -1) return;
          var pill = document.createElement('span');
          pill.className = 'pp-tag'; pill.setAttribute('data-v', v); pill.textContent = v;
          var x = document.createElement('button');
          x.type = 'button'; x.className = 'pp-tag-x'; x.setAttribute('aria-label', 'Remove'); x.innerHTML = '&times;';
          bindX(x); pill.appendChild(x);
          box.insertBefore(pill, input); sync();
        }
        Array.prototype.forEach.call(box.querySelectorAll('.pp-tag-x'), bindX);
        input.addEventListener('keydown', function(e){
          if (e.key === 'Enter' || e.key === sep) { e.preventDefault(); addTag(input.value); input.value = ''; }
          else if (e.key === 'Backspace' && input.value === '') { var t = box.querySelectorAll('.pp-tag'); if (t.length) { t[t.length - 1].remove(); sync(); } }
        });
        input.addEventListener('blur', function(){ if (input.value.trim()) { addTag(input.value); input.value = ''; } });
        box.addEventListener('mousedown', function(e){ if (e.target === box) { e.preventDefault(); input.focus(); } });
      });
    };
  }
  if (document.readyState !== 'loading') window.__ppTagsInit();
  else document.addEventListener('DOMContentLoaded', function(){ window.__ppTagsInit(); });
})();
</script>
HTML;
    }
}
