<?php
/**
 * PukiWiki plugin: Local-only checkboxes (no server-side storage)
 *
 * Inline plugins:
 *   &todo(id);                  // チェックボックスのみ
 *   &todo(id, ラベル);          // ラベル付き
 *   &todo();                    // id 自動採番（ラベル無し）
 *   &todo(, ラベル);            // id 自動採番（ラベルあり）
 *   &todo(-, ラベル);           // 同上（ハイフンは省略記号）
 *   &todo_clear();              // このページの &todo 状態を全消去するボタン
 *   &todo_clear(ボタン名);
 *
 * 仕様:
 *   - 状態は localStorage に保存（キー: `todo:<page>:<id>`）。サーバー送信なし。
 *   - id は手動指定も可能。自動採番時の安定性:
 *       * ラベルあり: `id = auto-` + md5(ラベル)
 *       * ラベルなし: `id = auto-n`（ページ内順序で連番; ページ内の順序変更でずれる可能性あり）
 *   - クリアボタンは同一ページの `todo:<page>:` で始まるキーを全削除します。
 *
 * Install:
 *   1) Save as plugin/todo.inc.php
 *   2) そのまま使えます（JS/CSSは自動挿入）
 */

if (!defined('PKWK_READONLY')) define('PKWK_READONLY', 0); // for compatibility

// --- Helpers ---------------------------------------------------------------
function plugin_todo_h($s){ return htmlspecialchars($s, ENT_QUOTES, 'UTF-8'); }
function plugin_todo_page(){ global $vars; return isset($vars['page']) ? $vars['page'] : ''; }

// Prevent injecting the script twice per page render
$GLOBALS['PLUGIN_TODO_JS_EMITTED'] = false;

function plugin_todo_emit_bootstrap_script(){
    if ($GLOBALS['PLUGIN_TODO_JS_EMITTED']) return '';
    $GLOBALS['PLUGIN_TODO_JS_EMITTED'] = true;
    $js = <<<EOS
<script>(function(){
  function initTodos(){
    try{
      var boxes = document.querySelectorAll('input.todo-box[type="checkbox"][data-key]');
      boxes.forEach(function(box){
        var key = box.getAttribute('data-key');
        var v = localStorage.getItem(key);
        box.checked = (v === '1');
        toggleLiClass(box);
        box.addEventListener('change', function(){
          localStorage.setItem(key, box.checked ? '1' : '0');
          toggleLiClass(box);
        });
      });

      function toggleLiClass(box){
        var li = box.closest('li');
        if(li){ li.classList.toggle('todo-done', box.checked); }
      }

      var clears = document.querySelectorAll('.todo-clear[data-page]');
      clears.forEach(function(btn){
        btn.addEventListener('click', function(){
          var page = btn.getAttribute('data-page');
          var prefix = 'todo:' + page + ':';
          // collect keys first to avoid iteration issues
          var del = [];
          for (var i=0; i<localStorage.length; i++){
            var k = localStorage.key(i);
            if (k && k.indexOf(prefix) === 0) del.push(k);
          }
          del.forEach(function(k){ localStorage.removeItem(k); });
          // このページのチェックをすべて外す
          document.querySelectorAll('input.todo-box[data-key]').forEach(function(b){
            var k = b.getAttribute('data-key');
            if (k && k.indexOf(prefix) === 0) {
              b.checked = false;
              toggleLiClass(b);
            }
          });
        });
      });
    }catch(e){ /* ignore */ }
  }
  if (document.readyState === 'loading') {
    document.addEventListener('DOMContentLoaded', initTodos);
  } else {
    initTodos();
  }
})();</script>
EOS;
    return $js;
}

// --- Inline plugin: &todo(id,label); --------------------------------------
$GLOBALS['PLUGIN_TODO_AUTO_SEQ'] = 0;
function plugin_todo_inline(){
    $args = func_get_args();
    if (isset($args[count($args)-1]) && is_array($args[count($args)-1])) array_pop($args);

    // 第1引数=id, 第2引数=ラベル, 第3引数=モード(clear)
    $provided = count($args) >= 1 ? trim((string)$args[0]) : '';
    $third    = count($args) >= 3 ? trim((string)$args[2]) : '';
    $is_clear = (strcasecmp($third, 'clear') === 0);

    if ($is_clear) {
        // クリアボタン: &todo(,,clear) / &todo(,任意ラベル,clear)
        $label = (count($args) >= 2 && trim((string)$args[1]) !== '') ? trim((string)$args[1]) : 'このページのチェックを全てクリア';
        $page  = plugin_todo_page();
        $html  = plugin_todo_emit_bootstrap_script();
        $html .= '<button type="button" class="todo-clear todo-clear-btn" data-page="' . plugin_todo_h($page) . '">' . plugin_todo_h($label) . '</button>';
        return $html;
    }

    // ---- 通常のチェックボックス処理 ----
    $label = '';
    if (count($args) >= 2) {
        // 2個目以降を「カンマを保ったまま」結合
        $label = trim(implode(',', array_slice($args, 1)));
        // 末尾だけに残った “,” は1個だけ除去（ラベル内のカンマは保持）
        if (mb_substr($label, -1) === ',') {
            $label = rtrim(mb_substr($label, 0, mb_strlen($label) - 1));
        }
    }

    $page = plugin_todo_page();

    // id 自動採番ロジック
    $id = $provided;
    if ($id === '' || $id === '-') {
        if ($label !== '') {
            $id = 'auto-' . substr(md5($label), 0, 10); // ラベル安定
        } else {
            $GLOBALS['PLUGIN_TODO_AUTO_SEQ'] = isset($GLOBALS['PLUGIN_TODO_AUTO_SEQ']) ? ($GLOBALS['PLUGIN_TODO_AUTO_SEQ'] + 1) : 1;
            $id = 'auto-' . $GLOBALS['PLUGIN_TODO_AUTO_SEQ']; // 表示順連番
        }
    }

    $key  = 'todo:' . $page . ':' . $id;
    $html = plugin_todo_emit_bootstrap_script();

    $input = '<input type="checkbox" class="todo-box" data-key="' . plugin_todo_h($key) . '" id="' . plugin_todo_h($key) . '"';
    if ($label === '') {
        $input .= ' aria-label="' . plugin_todo_h($id) . '" title="' . plugin_todo_h($id) . '"';
    }
    $input .= '>';

    if ($label === '') {
        $html .= '<span class="todo-item no-label">' . $input . '</span>';
    } else {
        $html .= '<span class="todo-item">' . $input
              .  '<label for="' . plugin_todo_h($key) . '">' . plugin_todo_h($label) . '</label>'
              .  '</span>';
    }
    return $html;
}


// --- Block plugin: #checklist{{ id: label ... }} ---------------------------
function plugin_checklist_convert(){
    $args = func_get_args();
    $body = '';
    // Last arg may be array of options. PukiWiki puts the block body in $args[0] or last.
    if (count($args) > 0){ $body = $args[0]; }
    if (!is_string($body)) $body = '';

    $lines = preg_split("/\r?\n/", $body);
    $page = plugin_todo_page();
    $buf = plugin_todo_emit_bootstrap_script();
    $buf .= '<ul class="todo-list">';

    foreach($lines as $ln){
        $ln = trim($ln);
        if ($ln === '') continue;
        // Allow formats: "id: label" or "id\tlabel"
        if (strpos($ln, ':') !== false){
            list($id, $label) = array_map('trim', explode(':', $ln, 2));
        } elseif (strpos($ln, "\t") !== false){
            list($id, $label) = array_map('trim', explode("\t", $ln, 2));
        } else {
            // if no separator, make a slug id from label
            $id = preg_replace('/\s+/', '-', mb_strtolower($ln));
            $label = $ln;
        }
        if ($id === '') continue;
        $key = 'todo:' . $page . ':' . $id;
        $buf .= '<li>'
             .  '<input type="checkbox" class="todo-box" data-key="' . plugin_todo_h($key) . '" id="' . plugin_todo_h($key) . '">'
             .  '<label for="' . plugin_todo_h($key) . '">' . plugin_todo_h($label) . '</label>'
             .  '</li>';
    }

    $buf .= '</ul>';
    return $buf;
}

// Backward compat alias (#todo{{ ... }}) if you prefer that tag name
function plugin_todo_convert(){
    $args = func_get_args();
    return call_user_func_array('plugin_checklist_convert', $args);
}

// クリアボタン: &todo_clear(); または &todo_clear(ボタン名);
function plugin_todo_clear_inline(){
    $args = func_get_args();
    if (isset($args[count($args)-1]) && is_array($args[count($args)-1])) array_pop($args);
    $label = (count($args) >= 1 && trim((string)$args[0]) !== '') ? trim((string)$args[0]) : 'このページのチェックを全てクリア';
    $page = plugin_todo_page();
    $html = plugin_todo_emit_bootstrap_script();
    $html .= '<button type="button" class="todo-clear todo-clear-btn" data-page="' . plugin_todo_h($page) . '">' . plugin_todo_h($label) . '</button>';
    return $html;
}
