<?php
namespace PpPhp\Plugins;

use PpPhp\Core\EditPlugin;

/**
 * Nepali Date Picker (Bikram Sambat) edit control.
 *
 * Wraps Sajan Maharjan's dependency-free "nepali.datepicker" v5 library
 * (bundled under assets/vendor/nepali-datepicker). Progressive enhancement:
 * a plain text input becomes an interactive BS calendar; if the library is
 * absent the field still accepts a typed BS date.
 *
 *  - single mode : just the Nepali (BS) picker for this field
 *  - double mode : two-way auto-sync with a chosen English (AD) date field —
 *                  picking a BS date fills the AD field (BS2AD) and editing the
 *                  AD field back-fills the BS picker (AD2BS), both via the
 *                  library's NepaliFunctions conversion engine.
 *
 * The BS value is stored as text (e.g. "2081-04-05") — BS dates are not
 * Gregorian, so a text column is the correct home.
 */
class NepaliDatePicker implements EditPlugin
{
    public static function id(): string { return 'NepaliDatePicker'; }
    public static function label(): string { return 'Nepali Date Picker'; }

    // ── Plugin Manager metadata (all optional across plugins) ──────────────
    public static function version(): string { return '1.0.0'; }
    public static function description(): string
    {
        return 'Bikram Sambat (Nepali) calendar date picker with optional two-way sync to an English (AD) date field. Bundles Sajan Maharjan\'s nepali.datepicker library locally — no CDN.';
    }
    public static function docsUrl(): string { return 'https://nepalidatepicker.sajanmaharjan.com.np/v5/'; }
    /** Bundled third-party assets + versions, for the Plugin Manager to show/track updates. */
    public static function vendor(): array
    {
        return [[
            'name'    => 'nepali.datepicker',
            'version' => '5.0.6',
            'author'  => 'Sajan Maharjan',
            'license' => 'MIT',
            'files'   => ['assets/vendor/nepali-datepicker/nepali.datepicker.min.js', 'assets/vendor/nepali-datepicker/nepali.datepicker.min.css'],
            'update'  => 'https://nepalidatepicker.sajanmaharjan.com.np/v5/  (drop the newer nepali.datepicker.vX.min.js/.css into public/assets/vendor/nepali-datepicker/, keeping the same filenames)',
        ]];
    }

    private static function flag(array $s, string $key, bool $default = false): bool
    {
        $v = $s[$key] ?? ($default ? 'yes' : 'no');
        return in_array(strtolower((string) $v), ['1', 'yes', 'true', 'on'], true);
    }

    public static function render(string $fieldName, mixed $value, array $settings): string
    {
        $fmt = (string) ($settings['dateFormat'] ?? 'YYYY-MM-DD');

        // Options handed verbatim to el.NepaliDatePicker(opts) (theme resolved client-side).
        $opts = [
            'dateFormat'       => $fmt,
            'language'         => (($settings['calendarLanguage'] ?? 'nepali') === 'english') ? 'english' : 'nepali',
            'unicodeDate'      => self::flag($settings, 'unicodeDigits'),
            'inline'           => self::flag($settings, 'inline'),
            'miniEnglishDates' => self::flag($settings, 'miniEnglishDates'),
            'disableToday'     => self::flag($settings, 'disableToday'),
            'theme'            => in_array(($settings['calendarTheme'] ?? 'auto'), ['light', 'dark'], true)
                                    ? $settings['calendarTheme'] : 'auto',
        ];
        foreach (['minDate', 'maxDate'] as $k) {
            $v = trim((string) ($settings[$k] ?? ''));
            if ($v !== '') $opts[$k] = $v;
        }
        foreach (['disableDaysBefore', 'disableDaysAfter'] as $k) {
            $v = $settings[$k] ?? '';
            if ($v !== '' && is_numeric($v)) $opts[$k] = (int) $v;
        }

        $pair = ($settings['pairMode'] ?? 'single') === 'double';
        $enField = $pair ? preg_replace('/[^A-Za-z0-9_]/', '', (string) ($settings['englishField'] ?? '')) : '';
        $enFmt = (string) ($settings['englishFormat'] ?? 'YYYY-MM-DD');

        $data = " data-pp-ndp='" . htmlspecialchars(json_encode($opts, JSON_UNESCAPED_SLASHES | JSON_HEX_APOS | JSON_HEX_QUOT), ENT_QUOTES, 'UTF-8') . "'";
        if ($enField !== '') {
            $data .= ' data-ndp-en="' . htmlspecialchars($enField) . '" data-ndp-en-format="' . htmlspecialchars($enFmt) . '"';
        }
        $ph = htmlspecialchars((string) ($settings['placeholder'] ?? ''));
        $safe = htmlspecialchars((string) ($value ?? ''));

        $input = '<input type="text" name="' . $fieldName . '" id="field_' . $fieldName . '" value="' . $safe . '"'
            . ' class="pp-input pp-ndp-input"' . $data . ' placeholder="' . $ph . '" autocomplete="off">';

        return $input . self::enhanceAssets();
    }

    public static function settingsSchema(): array
    {
        $doubleOnly = ['key' => 'pairMode', 'in' => ['double']];

        return [
            // ── Mode ─────────────────────────────────────────────
            ['key' => 'pairMode', 'type' => 'radiogroup', 'label' => 'Mode', 'options' => ['single', 'double'], 'default' => 'single', 'section' => 'Mode', 'hint' => 'Double: two-way sync with an English (AD) date field.'],
            ['key' => 'englishField', 'type' => 'maincolumn', 'label' => 'English (AD) field', 'default' => '', 'section' => 'Mode', 'showIf' => $doubleOnly, 'full' => true, 'hint' => 'The AD date field kept in sync with this BS field.'],
            ['key' => 'englishFormat', 'type' => 'select', 'label' => 'AD field format', 'options' => ['YYYY-MM-DD', 'DD/MM/YYYY', 'MM/DD/YYYY', 'YYYY/MM/DD', 'DD-MM-YYYY'], 'default' => 'YYYY-MM-DD', 'section' => 'Mode', 'showIf' => $doubleOnly],

            // ── Display ──────────────────────────────────────────
            ['key' => 'dateFormat', 'type' => 'select', 'label' => 'Date format', 'options' => ['YYYY-MM-DD', 'YYYY/MM/DD', 'YYYY.MM.DD', 'DD-MM-YYYY', 'DD/MM/YYYY', 'MM/DD/YYYY'], 'default' => 'YYYY-MM-DD', 'section' => 'Display'],
            ['key' => 'calendarLanguage', 'type' => 'radiogroup', 'label' => 'Calendar language', 'options' => ['nepali', 'english'], 'default' => 'nepali', 'section' => 'Display'],
            ['key' => 'calendarTheme', 'type' => 'radiogroup', 'label' => 'Calendar theme', 'options' => ['auto', 'light', 'dark'], 'default' => 'auto', 'section' => 'Display', 'hint' => 'Auto follows the app theme.'],
            ['key' => 'unicodeDigits', 'type' => 'bool', 'label' => 'Nepali digits (०१२)', 'default' => 'no', 'section' => 'Display'],
            ['key' => 'miniEnglishDates', 'type' => 'bool', 'label' => 'Show AD dates in cells', 'default' => 'no', 'section' => 'Display'],
            ['key' => 'inline', 'type' => 'bool', 'label' => 'Always-visible calendar', 'default' => 'no', 'section' => 'Display'],
            ['key' => 'placeholder', 'type' => 'text', 'label' => 'Placeholder', 'default' => '', 'section' => 'Display'],

            // ── Constraints ──────────────────────────────────────
            ['key' => 'minDate', 'type' => 'text', 'label' => 'Earliest BS date', 'default' => '', 'placeholder' => 'e.g. 2075-01-01', 'section' => 'Constraints'],
            ['key' => 'maxDate', 'type' => 'text', 'label' => 'Latest BS date', 'default' => '', 'placeholder' => 'e.g. 2090-12-30', 'section' => 'Constraints'],
            ['key' => 'disableToday', 'type' => 'bool', 'label' => 'Disable today', 'default' => 'no', 'section' => 'Constraints'],
            ['key' => 'disableDaysBefore', 'type' => 'number', 'label' => 'Disable days before today', 'default' => '', 'section' => 'Constraints'],
            ['key' => 'disableDaysAfter', 'type' => 'number', 'label' => 'Disable days after today', 'default' => '', 'section' => 'Constraints'],
        ];
    }

    public static function assets(): array
    {
        return [
            'js'  => ['assets/vendor/nepali-datepicker/nepali.datepicker.min.js'],
            'css' => ['assets/vendor/nepali-datepicker/nepali.datepicker.min.css'],
        ];
    }

    /**
     * One-time, guarded enhancement script (the self-enhancing-control contract).
     * Defines window.__ppNdpInit(root) so both the designer (renderer.js / the
     * config-modal preview) and the generated app can (re)enhance inputs. The
     * heavy library itself is loaded once via a <script src> (assets() above).
     */
    private static function enhanceAssets(): string
    {
        return <<<'HTML'
<style>
.pp-ndp-input{cursor:pointer}
.nepali-date-picker{z-index:9999}
</style>
<script>
(function(){
  if (window.__ppNdp) return; window.__ppNdp = true;
  function themeOf(want){
    if (want === 'light' || want === 'dark') return want;
    var r = document.documentElement, t = (r.getAttribute('data-pp-theme') || r.getAttribute('data-theme') || r.getAttribute('data-bs-theme') || '');
    if (/dark/i.test(t)) return 'dark';
    if (document.body && /dark/i.test(document.body.className)) return 'dark';
    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) return 'dark';
    return 'light';
  }
  function conv(fn, str, fromFmt, toFmt){
    try {
      if (typeof NepaliFunctions === 'undefined' || !str) return '';
      var p = NepaliFunctions.ParseDate(str, fromFmt);
      if (!p || !p.parsedDate) return '';
      var out = fn(p.parsedDate);
      return NepaliFunctions.ConvertToDateFormat(out, toFmt);
    } catch (e) { return ''; }
  }
  window.__ppNdpInit = function(root){
    root = root || document;
    if (typeof NepaliFunctions === 'undefined') return; // library not loaded → graceful plain input
    root.querySelectorAll('input[data-pp-ndp]').forEach(function(el){
      if (el._ppNdp) return; el._ppNdp = true;
      var opts = {}; try { opts = JSON.parse(el.getAttribute('data-pp-ndp') || '{}'); } catch (e) {}
      opts.mode = themeOf(opts.theme); delete opts.theme;
      var scope = el.closest('form') || root;
      var enName = el.getAttribute('data-ndp-en');
      var enFmt = el.getAttribute('data-ndp-en-format') || 'YYYY-MM-DD';
      var bsFmt = opts.dateFormat || 'YYYY-MM-DD';
      var enEl = enName ? scope.querySelector('[name="' + enName + '"]') : null;
      var busy = false;
      // Write the AD field — through flatpickr's API when it owns the input
      // (setting .value directly is ignored by flatpickr's internal state).
      function setAd(val){
        if (!enEl || !val) return;
        if (enEl._flatpickr) { enEl._flatpickr.setDate(val, false); }            // false = no change event → no loop
        else { enEl.value = val; enEl.dispatchEvent(new Event('input', {bubbles:true})); }
      }
      function forwardSync(){                    // BS → AD
        if (busy || !enEl) return; busy = true;
        try { var ad = conv(NepaliFunctions.BS2AD, el.value, bsFmt, enFmt); if (ad) setAd(ad); }
        finally { busy = false; }
      }
      function reverseSync(){                    // AD → BS
        if (busy) return; busy = true;
        try {
          var bs = conv(NepaliFunctions.AD2BS, enEl.value, enFmt, bsFmt);
          if (bs && bs !== el.value) { el.value = bs; try { el.NepaliDatePicker('destroy'); el.NepaliDatePicker(opts); } catch (e) {} }
        } finally { busy = false; }
      }
      // On pick: write the value and fire a real change event so BOTH the paired
      // AD field (via the change listener below) AND external consumers — the
      // list's spreadsheet/inline-edit save handlers — react.
      opts.onSelect = function(d){ if (d && d.value) el.value = d.value; el.dispatchEvent(new Event('change', {bubbles:true})); };
      try { el.NepaliDatePicker(opts); } catch (e) {}
      // forward sync (BS → AD) on any change to the BS field (pick or typing)
      if (enEl && !el._ppNdpFwd) { el._ppNdpFwd = true; el.addEventListener('change', forwardSync); }
      // reverse: AD field edited (its own picker or typing) → refresh the BS field
      if (enEl && !enEl._ppNdpBound) { enEl._ppNdpBound = true; enEl.addEventListener('change', reverseSync); }
    });
  };
  if (document.readyState !== 'loading') window.__ppNdpInit();
  else document.addEventListener('DOMContentLoaded', function(){ window.__ppNdpInit(); });
})();
</script>
HTML;
    }
}
