<?php
declare(strict_types=1);

/**
 * ============================================================
 *  プラグイン土台（ドロップイン方式）
 * ------------------------------------------------------------
 *  plugins/<name>/plugin.php を置くだけで有効になる。
 *  parts / frames のフォルダスキャンと同じ思想：再ビルド不要・
 *  フォルダを消せば無効化される。
 *
 *  plugin.php は「宣言だけ」を書く（DBアクセスや出力はしない）。
 *  下記の cms_register_* を呼んで、コアにフックを登録する：
 *    - cms_register_admin_nav()   管理画面のナビに項目を足す
 *    - cms_register_route()       公開ルーティング（一覧・詳細など）を足す
 *    - cms_register_migration()   専用テーブルの作成・初期化を足す
 *    - cms_register_parts_dir()   プラグイン同梱パーツのフォルダを足す
 *    - cms_register_part_renderer() 動的パーツ（DBから引くパーツ）の描画を足す
 *
 *  ※ 無料版/有料の出し分けは「別物として配る」方針（メモ 12.55b / 18-0）。
 *    ライセンスフラグで開閉しない＝この土台にゲート機構は持たない。
 * ============================================================
 */

/** プラグイン置き場 */
function cms_plugins_dir(): string {
    return CMS_ROOT . '/plugins';
}

/** 内部レジストリ（参照で共有） */
function &_cms_plugin_registry(): array {
    static $reg = [
        'loaded'        => false,
        'nav'           => [],   // key => ['label'=>, 'url'=>]
        'routes'        => [],   // callable[]   fn(string $slug): ?string
        'migrations'    => [],   // callable[]   fn(PDO): void
        'parts_dirs'    => [],   // string[]     パーツ定義フォルダ
        'renderers'     => [],   // partName => callable  fn(array $data, array $def, array $ctx): string|array
        'field_options' => [],   // name => callable  fn(): array  select等の動的選択肢
    ];
    return $reg;
}

/**
 * plugins/* を読み込む（1回だけ）。core/frame.php の末尾で呼ぶ。
 * この時点で config / db / parts / frame の関数は全て定義済み。
 */
function cms_load_plugins(): void {
    $reg =& _cms_plugin_registry();
    if ($reg['loaded']) return;
    $reg['loaded'] = true;

    $dir = cms_plugins_dir();
    if (!is_dir($dir)) return;

    $files = glob($dir . '/*/plugin.php') ?: [];
    sort($files); // 読み込み順を安定させる
    foreach ($files as $file) {
        require_once $file;
    }
}

/* ============================================================
 *  登録API（plugin.php から呼ぶ）
 * ============================================================ */

/** 管理画面ナビに項目を足す（メイン画像の直後に並ぶ） */
function cms_register_admin_nav(string $key, string $label, string $url): void {
    $reg =& _cms_plugin_registry();
    $key = preg_replace('/[^a-zA-Z0-9_\-]/', '', $key);
    if ($key === '') return;
    $reg['nav'][$key] = ['label' => $label, 'url' => $url];
}

/**
 * 公開ルーティングのハンドラを足す。
 * $cb(string $slug): ?string
 *   - このリクエストを引き受けるなら、完成したHTML（フルページ）を返す
 *   - 引き受けないなら null を返す（通常のページ表示に進む）
 * $_GET を見て判断してよい（例：?p=news&id=123 の id を見る）。
 */
function cms_register_route(callable $cb): void {
    $reg =& _cms_plugin_registry();
    $reg['routes'][] = $cb;
}

/**
 * マイグレーション（テーブル作成・初期データ）を足す。
 * $cb(PDO $pdo): void  ※ SQLite旧互換の作法で書くこと
 *   - CREATE TABLE IF NOT EXISTS / db_add_column_if_missing
 *   - 初期データの投入は settings のフラグ等で一度きりにする
 * db_migrate() の末尾で毎リクエスト呼ばれる前提＝必ず冪等にする。
 */
function cms_register_migration(callable $cb): void {
    $reg =& _cms_plugin_registry();
    $reg['migrations'][] = $cb;
}

/** プラグイン同梱パーツのフォルダを足す（part_defs が追加スキャンする） */
function cms_register_parts_dir(string $dir): void {
    $reg =& _cms_plugin_registry();
    if (is_dir($dir)) $reg['parts_dirs'][] = $dir;
}

/**
 * 動的パーツの描画を足す（DBから引くパーツ用）。
 * $cb(array $data, array $def, array $ctx): string|array
 *   - 文字列を返す＝そのパーツのHTMLを完全に差し替え
 *   - 配列を返す＝template.html へ渡すコンテキストに追記（マージ）
 * 通常の静的パーツ（template.html だけで描けるもの）は登録不要。
 */
function cms_register_part_renderer(string $partName, callable $cb): void {
    $reg =& _cms_plugin_registry();
    $reg['renderers'][$partName] = $cb;
}

/**
 * select等フィールドの「動的な選択肢」を供給するコールバックを登録。
 * $cb(): array  … [{value,label}, …] を返す（part.json の field に "options_from":"<name>"）。
 * これにより、part.json の静的optionsでは表せない「DBから引く選択肢」（例：リスト一覧）に対応。
 */
function cms_register_field_options(string $name, callable $cb): void {
    $reg =& _cms_plugin_registry();
    $reg['field_options'][$name] = $cb;
}

/** 登録済みの動的選択肢を取得（無ければ空配列） */
function cms_field_options(string $name): array {
    $reg =& _cms_plugin_registry();
    $cb = $reg['field_options'][$name] ?? null;
    if ($cb === null) return [];
    $opts = $cb();
    return is_array($opts) ? $opts : [];
}

/* ============================================================
 *  アクセサ（コア側が使う）
 * ============================================================ */

function cms_plugin_admin_nav(): array {
    $r =& _cms_plugin_registry();
    return $r['nav'];
}

function cms_plugin_parts_dirs(): array {
    $r =& _cms_plugin_registry();
    return $r['parts_dirs'];
}

/** 指定パーツの描画コールバック（無ければ null） */
function cms_part_renderer(string $partName): ?callable {
    $r =& _cms_plugin_registry();
    return $r['renderers'][$partName] ?? null;
}

/** 登録済みマイグレーションを全部実行（core/db.php の db_migrate 末尾で呼ぶ） */
function cms_run_plugin_migrations(PDO $pdo): void {
    $r =& _cms_plugin_registry();
    foreach ($r['migrations'] as $cb) {
        try {
            $cb($pdo);
        } catch (Throwable $e) {
            // 1つのプラグインの不備で全体を止めない（ログのみ）
            error_log('[plugin migration] ' . $e->getMessage());
        }
    }
}

/**
 * 公開ルーティング：登録ハンドラに順に尋ね、引き受けたらそのHTMLを返す。
 * どれも引き受けなければ null（＝通常のページ表示へ）。
 */
function cms_plugin_handle_route(string $slug): ?string {
    $r =& _cms_plugin_registry();
    foreach ($r['routes'] as $cb) {
        $html = $cb($slug);
        if (is_string($html)) return $html;
    }
    return null;
}
