<?php
namespace Core;

/**
 * 模型基类：同时支持 MySQL 与 文件(JSON) 两种存储
 * 子类设置 $table 与 $orderBy 即可。
 */
class Model
{
    protected $table;
    protected $pk = 'id';
    protected $orderBy = 'id';

    /* ---------- 文件模式 ---------- */
    private function file(): string
    {
        return Db::fileDir() . '/' . $this->table . '.json';
    }
    private function read(): array
    {
        $f = $this->file();
        if (!is_file($f)) return [];
        $d = json_decode(file_get_contents($f), true);
        return is_array($d) ? $d : [];
    }
    private function write(array $rows): void
    {
        $rows = $this->sanitizeUtf8($rows);
        // JSON_INVALID_UTF8_SUBSTITUTE (PHP 7.2+) 保证即使存在非法 UTF-8 也不会让 json_encode 返回 false，
        // 避免 file_put_contents(false) 把整个数据文件清空为 0 字节（灾难性数据丢失）。
        $json = json_encode($rows, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT | JSON_INVALID_UTF8_SUBSTITUTE);
        if ($json === false || $json === '') {
            error_log('[Model::write] json_encode failed for ' . $this->table . '; aborting write to avoid data loss');
            return;
        }
        $f = $this->file();
        $dir = dirname($f);
        if (!is_dir($dir)) {
            @mkdir($dir, 0755, true);
        }
        // 写入失败（最常见：服务器目录/文件权限不足，PHP 进程无写权限）必须记录日志，
        // 否则会“假成功”——页面跳转回去、用户以为保存了，数据却没变。
        $bytes = @file_put_contents($f, $json);
        if ($bytes === false) {
            $err = error_get_last();
            error_log('[Model::write] FAILED to write ' . $f . ' — ' . ($err['message'] ?? 'unknown error') .
                ' | 请检查目录/文件所有者是否为 PHP 运行用户（宝塔通常是 www）并赋予写权限');
        }
    }

    /** 递归将数组中的字符串修复为合法 UTF-8，剔除非法字节序列 */
    private function sanitizeUtf8($v)
    {
        if (is_array($v)) {
            return array_map([$this, 'sanitizeUtf8'], $v);
        }
        if (is_string($v) && !mb_check_encoding($v, 'UTF-8')) {
            return mb_convert_encoding($v, 'UTF-8', 'UTF-8');
        }
        return $v;
    }
    private function sort(array &$rows): void
    {
        $col = $this->orderBy;
        usort($rows, function ($a, $b) use ($col) {
            $va = $a[$col] ?? 0; $vb = $b[$col] ?? 0;
            return $va <=> $vb;
        });
    }

    /* ---------- 通用 CRUD ---------- */
    public function all(): array
    {
        if (Db::driver() === 'mysql') {
            return Db::query("SELECT * FROM `{$this->table}` ORDER BY `{$this->orderBy}` ASC")->fetchAll();
        }
        $rows = $this->read(); $this->sort($rows); return $rows;
    }

    public function find($id)
    {
        if (Db::driver() === 'mysql') {
            return Db::query("SELECT * FROM `{$this->table}` WHERE `{$this->pk}`=?", [$id])->fetch() ?: null;
        }
        foreach ($this->read() as $r) if (($r[$this->pk] ?? null) == $id) return $r;
        return null;
    }

    public function where(string $col, $val)
    {
        if (Db::driver() === 'mysql') {
            return Db::query("SELECT * FROM `{$this->table}` WHERE `{$col}`=?", [$val])->fetch() ?: null;
        }
        foreach ($this->read() as $r) if (($r[$col] ?? null) == $val) return $r;
        return null;
    }

    public function whereAll(string $col, $val): array
    {
        if (Db::driver() === 'mysql') {
            return Db::query("SELECT * FROM `{$this->table}` WHERE `{$col}`=? ORDER BY `{$this->orderBy}` ASC", [$val])->fetchAll();
        }
        $out = []; foreach ($this->read() as $r) if (($r[$col] ?? null) == $val) $out[] = $r;
        $this->sort($out); return $out;
    }

    public function insert(array $data)
    {
        if (Db::driver() === 'mysql') {
            $cols = array_keys($data);
            $sql = "INSERT INTO `{$this->table}` (`" . implode('`,`', $cols) . "`) VALUES (" . implode(',', array_fill(0, count($cols), '?')) . ")";
            Db::query($sql, array_values($data));
            return Db::pdo()->lastInsertId();
        }
        $rows = $this->read();
        $id = $rows ? (max(array_column($rows, $this->pk)) + 1) : 1;
        $data[$this->pk] = $id;
        $rows[] = $data; $this->write($rows);
        return $id;
    }

    public function update($id, array $data): void
    {
        if (Db::driver() === 'mysql') {
            $cols = array_keys($data);
            $sql = "UPDATE `{$this->table}` SET `" . implode('`=?,`', $cols) . "`=? WHERE `{$this->pk}`=?";
            Db::query($sql, array_merge(array_values($data), [$id]));
            return;
        }
        $rows = $this->read();
        foreach ($rows as &$r) {
            if (($r[$this->pk] ?? null) == $id) { $r = array_merge($r, $data); break; }
        }
        $this->write($rows);
    }

    public function delete($id): void
    {
        if (Db::driver() === 'mysql') {
            Db::query("DELETE FROM `{$this->table}` WHERE `{$this->pk}`=?", [$id]);
            return;
        }
        $rows = array_filter($this->read(), fn($r) => ($r[$this->pk] ?? null) != $id);
        $this->write(array_values($rows));
    }

    /** 按任意列批量删除（用于主从表级联删除从表） */
    public function deleteRaw(string $col, $val): void
    {
        if (Db::driver() === 'mysql') {
            Db::query("DELETE FROM `{$this->table}` WHERE `{$col}`=?", [$val]);
            return;
        }
        $rows = array_filter($this->read(), fn($r) => ($r[$col] ?? null) != $val);
        $this->write(array_values($rows));
    }

    public function count(): int
    {
        return count($this->all());
    }
}
