<?php
declare(strict_types=1);

/**
 * ============================================================
 *  Frames CMS  -  共通設定 / ブートストラップ
 * ------------------------------------------------------------
 *  - PHP 8.0+ / SQLite 前提
 *  - レンタルサーバーにフォルダごとアップすればそのまま動く設計
 *  - サブディレクトリ設置に自動対応（BASE_URL 自動検出）
 * ============================================================
 */

if (defined('CMS_BOOTED')) return;
define('CMS_BOOTED', true);

mb_internal_encoding('UTF-8');
date_default_timezone_set('Asia/Tokyo');

/* ---------- パス定数（このファイルがプロジェクト直下にある前提） ---------- */
define('CMS_ROOT',    __DIR__);
define('CMS_CORE',    CMS_ROOT . '/core');
define('CMS_FRAMES',  CMS_ROOT . '/frames');
define('CMS_PARTS',   CMS_ROOT . '/parts');

/** デモモード：ルート直下に `.demo_mode` マーカーがあればON（デモ配布サーバー専用の安全策）。
 *  ON時：外部タグ(head/body code)の出力・保存を無効化／フォームの実メール送信を停止。
 *  マーカーが無い製品版・無料版では常に false ＝挙動は一切変わらない（純粋な追加ガード）。 */
function cms_demo_mode(): bool {
    static $on = null;
    // ルートに `.demo_mode`（隠し）または `demo_mode.txt`（見える）があればON。どちらでも可。
    if ($on === null) $on = is_file(CMS_ROOT . '/.demo_mode') || is_file(CMS_ROOT . '/demo_mode.txt');
    return $on;
}

/** 公開プレビュー版（ルートに `siyou.txt` があればON）。無料版を「実際のサイト」として誰でも閲覧できるよう常設公開する用途。
 *  ON時：管理画面(/admin)を丸ごと遮断（ログイン・初回アカウント作成とも）／フォームの実メール送信を停止。
 *  ＝閲覧専用になるので、書き換え・アップロード・乗っ取りが原理的に起きない。マーカーが無ければ挙動は一切変わらない（配布zipには入れない）。 */
function cms_preview_mode(): bool {
    static $on = null;
    if ($on === null) $on = is_file(CMS_ROOT . '/siyou.txt');
    return $on;
}

/* ▼▼ 設置時に各自で固有名へ変更（推測されにくくする＝nginx等でのDB直接ダウンロード対策）▼▼
 *  ・最低限：$cms_db_file（DBファイル名）を変える。
 *  ・より強固に：$cms_data_dir（データフォルダ名）も変える ※その場合は実際のフォルダ名も同じ名前にリネームすること。
 *  詳しくは導入マニュアルを参照。 */
$cms_data_dir = 'data';            // 例: 'data_k8s3p'
$cms_db_file  = 'site.sqlite3';    // 例: 'store_9x7q2.sqlite3'
/* ▲▲ ここまで ▲▲ */

define('CMS_DATA',    CMS_ROOT . '/' . $cms_data_dir);
define('CMS_UPLOADS', CMS_ROOT . '/uploads');
define('CMS_DB',      CMS_DATA . '/' . $cms_db_file);

/* ---------- 必要ディレクトリの自動作成（data には保護用 .htaccess も自動設置） ---------- */
foreach ([CMS_DATA, CMS_UPLOADS] as $d) {
    if (!is_dir($d)) @mkdir($d, 0775, true);
}
/* data フォルダ名を変えても保護が外れないよう、.htaccess が無ければ書き込む（Apache/LiteSpeed 用の直接アクセス禁止）。
 * ※nginx は .htaccess を読まないため、固有名化（推測困難化）が主防御になる。 */
$cms_data_ht = CMS_DATA . '/.htaccess';
if (is_dir(CMS_DATA) && !is_file($cms_data_ht)) {
    @file_put_contents(
        $cms_data_ht,
        "# データ（SQLite DB等）への直接アクセスを禁止\n"
        . "<IfModule mod_authz_core.c>\n  Require all denied\n</IfModule>\n"
        . "<IfModule !mod_authz_core.c>\n  Order allow,deny\n  Deny from all\n</IfModule>\n"
    );
}

/* ---------- エラー表示（本番は非表示・ログのみ） ----------
 *  開発中に画面でエラーを見たい時は、このファイルの先頭付近で
 *  define('CMS_DEBUG', true); しておく。
 *  ※ data/ は .htaccess で公開アクセス禁止なので、ログ置き場として安全。 */
if (!defined('CMS_DEBUG')) define('CMS_DEBUG', false);
error_reporting(E_ALL);
if (CMS_DEBUG) {
    @ini_set('display_errors', '1');
} else {
    @ini_set('display_errors', '0');
    @ini_set('log_errors', '1');
    @ini_set('error_log', CMS_DATA . '/php-error.log');
}

/* ============================================================
 *  BASE_URL 自動検出
 *  例) ドメイン直下に設置  -> ""      （リンクは "/about" 等）
 *      /cms/ サブディレクトリ -> "/cms"
 *  DOCUMENT_ROOT とプロジェクト実パスの差分から算出（管理画面/フロント
 *  どちらから呼ばれても同じ値になるのが利点）。失敗時は SCRIPT_NAME で補完。
 * ============================================================ */
(function (): void {
    $base = null;

    $doc = $_SERVER['DOCUMENT_ROOT'] ?? '';
    $doc = is_string($doc) && $doc !== '' ? (realpath($doc) ?: '') : '';
    $root = realpath(CMS_ROOT) ?: CMS_ROOT;

    if ($doc !== '') {
        $docN  = rtrim(str_replace('\\', '/', $doc), '/');
        $rootN = rtrim(str_replace('\\', '/', $root), '/');
        if ($rootN === $docN) {
            $base = '';
        } elseif (str_starts_with($rootN, $docN . '/')) {
            $base = substr($rootN, strlen($docN)); // 例: /cms
        }
    }

    if ($base === null) {
        // フォールバック: 実行スクリプト位置から推定
        $script = str_replace('\\', '/', $_SERVER['SCRIPT_NAME'] ?? '');
        $dir = rtrim(dirname($script), '/');
        // /admin 配下から呼ばれた場合は1階層上がプロジェクト直下
        if (preg_match('#/admin$#', $dir)) {
            $dir = rtrim(dirname($dir), '/');
        }
        $base = ($dir === '/' || $dir === '.') ? '' : $dir;
    }

    define('BASE_URL', $base); // 末尾スラッシュ無し（"" もしくは "/cms"）
})();

/** ベースURL付きのリンクを作る */
function url(string $path = ''): string {
    $path = '/' . ltrim($path, '/');
    if ($path === '/') $path = '/';
    return BASE_URL . $path;
}

/** ページへのリンク（クエリ方式ルーティング） */
function page_url(string $slug): string {
    if ($slug === '' || $slug === 'home') return url('/');
    return url('/') . '?p=' . rawurlencode($slug);
}

/* ---------- セッション ---------- */
if (session_status() !== PHP_SESSION_ACTIVE) {
    $secure = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off');
    session_set_cookie_params([
        'lifetime' => 0,
        'path'     => BASE_URL === '' ? '/' : BASE_URL . '/',
        'secure'   => $secure,
        'httponly' => true,
        'samesite' => 'Lax',
    ]);
    session_name('cms_sess');
    @session_start();
}

/* ---------- 最小セキュリティヘッダ ---------- */
if (!headers_sent()) {
    header('X-Content-Type-Options: nosniff');
    header('X-Frame-Options: SAMEORIGIN');
    header('Referrer-Policy: same-origin');
}

/* ============================================================
 *  共通ヘルパー
 * ============================================================ */

/** HTMLエスケープ */
function h(?string $s): string {
    return htmlspecialchars($s ?? '', ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

/** 拡張子で動画判定 */
function is_video_file(string $name): bool {
    return in_array(strtolower(pathinfo($name, PATHINFO_EXTENSION)), ['mp4', 'webm', 'ogg'], true);
}

/** 拡張子で画像判定 */
function is_image_file(string $name): bool {
    return in_array(strtolower(pathinfo($name, PATHINFO_EXTENSION)), ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'], true);
}

/** アップロードファイルの公開URL */
function upload_url(string $name): string {
    return url('/uploads/' . rawurlencode($name));
}

/** CSRFトークン取得 */
function csrf_token(): string {
    if (empty($_SESSION['_csrf'])) {
        $_SESSION['_csrf'] = bin2hex(random_bytes(32));
    }
    return $_SESSION['_csrf'];
}

/** CSRF検証 */
function csrf_check(?string $token): bool {
    return is_string($token) && !empty($_SESSION['_csrf']) && hash_equals($_SESSION['_csrf'], $token);
}

/** JSONを安全に配列で読む */
function json_array(?string $raw): array {
    if (!is_string($raw) || $raw === '') return [];
    $v = json_decode($raw, true);
    return is_array($v) ? $v : [];
}

/**
 * 致命的エラー時に、真っ白ではなく日本語の案内画面を出して停止する。
 * $message は簡単なHTML可（<code> 等）。技術的な詳細は出さない。
 */
function cms_error_page(string $title, string $message): void {
    if (!headers_sent()) {
        http_response_code(500);
        header('Content-Type: text/html; charset=UTF-8');
    }
    echo '<!DOCTYPE html><html lang="ja"><head><meta charset="UTF-8">';
    echo '<meta name="viewport" content="width=device-width, initial-scale=1">';
    echo '<title>' . h($title) . '</title><style>';
    echo 'body{font-family:-apple-system,"Hiragino Kaku Gothic ProN",Meiryo,sans-serif;background:#f8fafc;color:#1f2937;margin:0;line-height:1.85}';
    echo '.box{max-width:640px;margin:8vh auto;background:#fff;border:1px solid #e5e7eb;border-radius:14px;padding:2rem 2.2rem}';
    echo 'h1{font-size:1.25rem;margin:0 0 1rem;color:#b91c1c}';
    echo '.muted{color:#6b7280;font-size:.9rem;margin-top:1.5rem}';
    echo 'code{background:#eef2f7;border-radius:4px;padding:.1em .4em}';
    echo '</style></head><body><div class="box"><h1>' . h($title) . '</h1>';
    echo '<p>' . $message . '</p>';
    echo '<p class="muted">解決しない場合は、サーバーのPHPバージョン・SQLite（pdo_sqlite）・data/uploadsフォルダの書き込み権限をご確認ください。</p>';
    echo '</div></body></html>';
    exit;
}
