<?php
/**
 * i18n_core.php - 통합 국제화 라이브러리 (비압축 버전)
 * 구성: streams, gettext, plugin, l10n
 * 특징: 보안 강화, PHP 8.2+ 호환, 상속 오류 해결
 */

// --- 1. Streams 라이브러리 (데이터 읽기 계층) ---

/**
 * 모든 리더의 최상위 부모 클래스
 */
#[AllowDynamicProperties]
class StreamReader {
    public $error = 0;
    public function read($bytes) { return false; }
    public function seekto($position) { return false; }
    public function currentpos() { return 0; }
    public function length() { return 0; }

    /**
     * gettext_reader에서 사용하는 4바이트 정수 읽기 함수
     */
    public function readint($byteorder = 0) {
        $data = $this->read(4);
        if (strlen($data) < 4) return 0;
        $res = ($byteorder) ? unpack('N', $data) : unpack('V', $data);
        return $res[1];
    }
}

/**
 * 문자열 기반 리더
 */
#[AllowDynamicProperties]
class StringReader extends StreamReader {
    protected $_pos = 0;
    protected $_str;
    protected $is_overloaded;

    public function __construct($str = '') {
        $this->_str = $str;
        $this->is_overloaded = function_exists('mb_substr');
    }

    public function read($bytes) {
        $data = $this->is_overloaded ? 
                mb_substr($this->_str, $this->_pos, $bytes, 'ascii') : 
                substr($this->_str, $this->_pos, $bytes);
        $this->_pos += $bytes;
        $len = $this->length();
        if ($len < $this->_pos) $this->_pos = $len;
        return $data;
    }

    public function seekto($pos) {
        $this->_pos = $pos;
        $len = $this->length();
        if ($len < $this->_pos) $this->_pos = $len;
        return $this->_pos;
    }

    public function currentpos() { return $this->_pos; }
    public function length() {
        return $this->is_overloaded ? mb_strlen($this->_str, 'ascii') : strlen($this->_str);
    }
}

/**
 * 보안이 강화된 파일 기반 리더
 */
#[AllowDynamicProperties]
class SafeFileReader extends StreamReader {
    protected $_pos = 0;
    protected $_fd;
    protected $_length = 0;

    public function __construct($filename) {
        $realPath = realpath($filename);
        // [보안] 파일 존재 여부 및 경로 조작 방지
        if (!$realPath || !is_file($realPath)) {
            $this->error = 2; // File not found
            return;
        }
        $this->_length = filesize($realPath);
        $this->_fd = fopen($realPath, 'rb');
        if (!$this->_fd) $this->error = 3;
    }

    public function read($bytes) {
        if (!$this->_fd || $bytes <= 0) return '';
        // [보안] 자원 고갈 방지를 위한 최대 읽기 제한 (2MB)
        $bytes = min($bytes, 2097152);
        fseek($this->_fd, $this->_pos);
        $data = fread($this->_fd, $bytes);
        $this->_pos = ftell($this->_fd);
        return $data;
    }

    public function seekto($pos) {
        if ($this->_fd) {
            fseek($this->_fd, $pos);
            $this->_pos = ftell($this->_fd);
        }
        return $this->_pos;
    }

    public function currentpos() { return $this->_pos; }
    public function length() { return $this->_length; }
    public function close() { if ($this->_fd) fclose($this->_fd); }
}

// 기존 하위 호환을 위한 클래스 별칭
class FileReader extends SafeFileReader {}

/**
 * 메모리 성능을 위한 캐싱 리더
 */
class CachedFileReader extends StringReader {
    public function __construct($filename) {
        parent::__construct();
        if (is_file($filename)) {
            // [보안] 10MB 이상의 파일은 메모리 보호를 위해 캐싱 거부
            if (filesize($filename) > 10485760) {
                $this->error = 4;
                return;
            }
            $this->_str = file_get_contents($filename);
            $this->_pos = 0;
        } else {
            $this->error = 2;
        }
    }
}

// --- 2. Gettext 라이브러리 (MO 파일 파싱 계층) ---

#[AllowDynamicProperties]
class gettext_reader {
    public $error = 0;
    private $BO = 0; // Byte Order
    private $S;      // Stream
    private $sc = false; // Short circuit
    private $ec = true;  // Enable cache
    public $total = 0;
    private $tO; // table_originals
    private $tT; // table_translations
    public $cache = [];

    public function __construct($reader, $enable_cache = true) {
        if (!$reader || $reader->error) {
            $this->sc = true;
            return;
        }
        $this->S = $reader;
        $this->ec = $enable_cache;

        // 매직 넘버 확인을 통한 엔디안 판별
        $magic = $this->S->readint();
        if ($magic == (int)-1794895138 || $magic == (int)2500072158) {
            $this->BO = 0; // Little Endian
        } elseif ($magic == ((int)-569244523 & 0xFFFFFFFF)) {
            $this->BO = 1; // Big Endian
        } else {
            $this->error = 1;
            return;
        }

        $this->S->readint(); // revision
        $this->total = $this->S->readint($this->BO);
        $this->tO = $this->S->readint($this->BO);
        $this->tT = $this->S->readint($this->BO);

        $this->load_tables();
    }

    public function load_tables() {
        // 원문 인덱스 로드
        $this->S->seekto($this->tO);
        $table_originals = [];
        for ($i = 0; $i < $this->total; $i++) {
            $table_originals[$i * 2 + 1] = $this->S->readint($this->BO);
            $table_originals[$i * 2 + 2] = $this->S->readint($this->BO);
        }

        // 번역문 인덱스 로드
        $this->S->seekto($this->tT);
        $table_translations = [];
        for ($i = 0; $i < $this->total; $i++) {
            $table_translations[$i * 2 + 1] = $this->S->readint($this->BO);
            $table_translations[$i * 2 + 2] = $this->S->readint($this->BO);
        }

        // 캐시 활성화 시 번역문 미리 로드
        if ($this->ec) {
            for ($i = 0; $i < $this->total; $i++) {
                $orig = $this->get_string($this->S, $table_originals, $i);
                $trans = $this->get_string($this->S, $table_translations, $i);
                $this->cache[$orig] = $trans;
            }
        }
    }

    private function get_string($stream, $table, $idx) {
        $length = $table[$idx * 2 + 1];
        $offset = $table[$idx * 2 + 2];
        $stream->seekto($offset);
        return (string)$stream->read($length);
    }

    public function translate($string) {
        if ($this->sc) return $string;
        return $this->cache[$string] ?? $string;
    }
}

// --- 3. Plugin 라이브러리 (훅/필터 계층) ---

$wp_filter = [];
$merged_filters = [];
$wp_current_filter = [];

function add_filter($tag, $function, $priority = 10, $accepted_args = 1) {
    global $wp_filter, $merged_filters;
    $idx = _wp_filter_id($tag, $function, $priority);
    $wp_filter[$tag][$priority][$idx] = ['function' => $function, 'accepted_args' => $accepted_args];
    unset($merged_filters[$tag]);
    return true;
}

function apply_filters($tag, $value, ...$args) {
    global $wp_filter, $merged_filters, $wp_current_filter;
    $wp_current_filter[] = $tag;
    if (!isset($wp_filter[$tag])) {
        array_pop($wp_current_filter);
        return $value;
    }
    if (!isset($merged_filters[$tag])) {
        ksort($wp_filter[$tag]);
        $merged_filters[$tag] = true;
    }
    foreach ($wp_filter[$tag] as $priority => $functions) {
        foreach ($functions as $f) {
            $value = call_user_func_array($f['function'], array_slice([$value, ...$args], 0, $f['accepted_args']));
        }
    }
    array_pop($wp_current_filter);
    return $value;
}

function _wp_filter_id($tag, $f, $p) {
    if (is_string($f)) return $f;
    if (is_array($f)) return (is_object($f[0]) ? spl_object_hash($f[0]) : $f[0]) . $f[1];
    return spl_object_hash($f);
}

function add_action($tag, $f, $p = 10, $a = 1) { return add_filter($tag, $f, $p, $a); }

// --- 4. L10n 라이브러리 (사용자 인터페이스 계층) ---

$l10n = [];

function get_locale() {
    static $locale = null;
    if ($locale !== null) return apply_filters('locale', $locale);
    $locale = defined('WPLANG') ? WPLANG : 'en_US';
    return apply_filters('locale', $locale);
}

function __($text, $domain = 'default') {
    global $l10n;
    if (isset($l10n[$domain])) return $l10n[$domain]->translate($text);
    return $text;
}

function _e($text, $domain = 'default') {
    echo __($text, $domain);
}

function load_textdomain($domain, $mofile) {
    global $l10n;
    if (!is_readable($mofile)) return false;
    
    $reader = new CachedFileReader($mofile);
    if ($reader->error) return false;
    
    $gettext = new gettext_reader($reader);
    $l10n[$domain] = $gettext;
    return true;
}
