<?php
/**
 * ファイル内メタデータ（APP_VERSION / APP_CATEGORY / APP_DESCRIPTION / APP_MEMO）
 */
const FL_META_KEYS = [
    'APP_VERSION',
    'APP_CATEGORY',
    'APP_DESCRIPTION',
    'APP_MEMO',
];

function fl_meta_empty(): array
{
    return [
        'APP_VERSION'     => '',
        'APP_CATEGORY'    => '',
        'APP_DESCRIPTION' => '',
        'APP_MEMO'        => '',
    ];
}

function fl_meta_normalize_field(string $field): string
{
    $field = strtoupper(trim($field));

    if ($field === 'DESCRIPTION') {
        return 'APP_DESCRIPTION';
    }

    return in_array($field, FL_META_KEYS, true) ? $field : '';
}

function fl_format_version_from_mtime($mtime)
{
    return date('Y.m.d', (int) $mtime);
}

function fl_display_version(array $meta, $filename, $mtime)
{
    if ($meta['APP_VERSION'] !== '') {
        return $meta['APP_VERSION'];
    }

    $name = pathinfo($filename, PATHINFO_FILENAME);
    $parts = explode('_', $name, 2);
    $ver = isset($parts[0]) ? $parts[0] : '';

    if ($ver !== '' && preg_match('/^\d/', $ver)) {
        return $ver;
    }

    return fl_format_version_from_mtime($mtime);
}

function fl_read_meta(string $file): array
{
    $meta = fl_meta_empty();

    if (!is_file($file)) {
        return $meta;
    }

    $txt = @file_get_contents($file);

    if ($txt === false) {
        return $meta;
    }

    $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));

    if ($ext === 'php') {
        return fl_read_meta_php($txt);
    }

    if (in_array($ext, ['txt', 'md', 'log'], true)) {
        return fl_read_meta_txt($txt);
    }

    if (in_array($ext, ['html', 'htm'], true)) {
        return fl_read_meta_html($txt);
    }

    if ($ext === 'js') {
        return fl_read_meta_js($txt);
    }

    if ($ext === 'css') {
        return fl_read_meta_css($txt);
    }

    if ($ext === 'json') {
        return fl_read_meta_json($txt);
    }

    return $meta;
}

function fl_read_meta_php(string $txt): array
{
    $meta = fl_meta_empty();

    foreach (FL_META_KEYS as $key) {
        $q = preg_quote($key, '/');
        if (preg_match("/define\s*\(\s*'{$q}'\s*,\s*'((?:\\\\'|[^'])*)'\s*\)/", $txt, $m)) {
            $meta[$key] = stripcslashes($m[1]);
            continue;
        }
        if (preg_match('/define\s*\(\s*"' . $q . '"\s*,\s*"((?:\\\\"|[^"])*)"\s*\)/', $txt, $m)) {
            $meta[$key] = stripcslashes($m[1]);
        }
    }

    if ($meta['APP_DESCRIPTION'] === '') {
        foreach (['APP_Description', 'Description'] as $legacy) {
            $q = preg_quote($legacy, '/');
            if (preg_match("/define\s*\(\s*'{$q}'\s*,\s*'((?:\\\\'|[^'])*)'\s*\)/", $txt, $m)) {
                $meta['APP_DESCRIPTION'] = stripcslashes($m[1]);
                break;
            }
        }
    }

    return $meta;
}

function fl_read_meta_txt(string $txt): array
{
    $meta = fl_meta_empty();
    $keys = implode('|', FL_META_KEYS);

    foreach (preg_split('/\r?\n/', $txt) as $line) {
        if (preg_match("/^#\s*({$keys}|Description):\s*(.*)$/", $line, $m)) {
            $key = $m[1] === 'Description' ? 'APP_DESCRIPTION' : $m[1];
            $meta[$key] = $m[2];
            continue;
        }

        if (trim($line) === '') {
            continue;
        }

        break;
    }

    return $meta;
}

function fl_read_meta_html(string $txt): array
{
    $meta = fl_meta_empty();

    foreach (FL_META_KEYS as $key) {
        if (preg_match('/<!--\s*' . preg_quote($key, '/') . ':\s*(.*?)\s*-->/s', $txt, $m)) {
            $meta[$key] = trim($m[1]);
        }
    }

    if ($meta['APP_DESCRIPTION'] === '' && preg_match('/<!--\s*Description:\s*(.*?)\s*-->/s', $txt, $m)) {
        $meta['APP_DESCRIPTION'] = trim($m[1]);
    }

    return $meta;
}

function fl_read_meta_js(string $txt): array
{
    $meta = fl_meta_empty();
    $keys = implode('|', FL_META_KEYS);

    if (preg_match_all('/\/\/\s*(' . $keys . '|Description):\s*(.*)$/m', $txt, $ms, PREG_SET_ORDER)) {
        foreach ($ms as $m) {
            $key = $m[1] === 'Description' ? 'APP_DESCRIPTION' : $m[1];
            $meta[$key] = $m[2];
        }
    }

    return $meta;
}

function fl_read_meta_css(string $txt): array
{
    $meta = fl_meta_empty();

    if (preg_match('/\/\*\s*([\s\S]*?)\s*\*\//', $txt, $block)) {
        foreach (preg_split('/\r?\n/', $block[1]) as $line) {
            if (preg_match('/^\s*(APP_VERSION|APP_CATEGORY|APP_DESCRIPTION|APP_MEMO|Description):\s*(.*)$/', trim($line), $m)) {
                $key = $m[1] === 'Description' ? 'APP_DESCRIPTION' : $m[1];
                $meta[$key] = $m[2];
            }
        }
    }

    return $meta;
}

function fl_read_meta_json(string $txt): array
{
    $meta = fl_meta_empty();
    $data = json_decode($txt, true);

    if (!is_array($data)) {
        return $meta;
    }

    foreach (FL_META_KEYS as $key) {
        if (!empty($data[$key])) {
            $meta[$key] = (string) $data[$key];
        }
    }

    if ($meta['APP_DESCRIPTION'] === '' && !empty($data['Description'])) {
        $meta['APP_DESCRIPTION'] = (string) $data['Description'];
    }

    return $meta;
}

function fl_escape_php_string(string $value): string
{
    return str_replace(["\\", "'"], ["\\\\", "\\'"], $value);
}

function fl_split_txt_body(string $txt): array
{
    $meta = fl_meta_empty();
    $body = [];
    $keys = implode('|', FL_META_KEYS);

    foreach (preg_split('/\r?\n/', $txt) as $line) {
        if (preg_match("/^#\s*({$keys}|Description):\s*(.*)$/", $line, $m)) {
            $key = $m[1] === 'Description' ? 'APP_DESCRIPTION' : $m[1];
            $meta[$key] = $m[2];
            continue;
        }

        $body[] = $line;
    }

    return [$meta, ltrim(implode("\n", $body), "\n")];
}

function fl_build_txt_header(array $meta): string
{
    $lines = [];

    foreach (FL_META_KEYS as $key) {
        if ($meta[$key] !== '') {
            $lines[] = '# ' . $key . ': ' . $meta[$key];
        }
    }

    if ($lines === []) {
        return '';
    }

    return implode("\n", $lines) . "\n\n";
}

function fl_write_meta(string $file, string $field, string $value): bool
{
    $field = fl_meta_normalize_field($field);

    if ($field === '' || !is_file($file)) {
        return false;
    }

    $txt = file_get_contents($file);

    if ($txt === false) {
        return false;
    }

    $ext = strtolower(pathinfo($file, PATHINFO_EXTENSION));
    $meta = fl_read_meta($file);
    $meta[$field] = $value;

    if ($ext === 'php') {
        $new_txt = fl_write_meta_php($txt, $meta);
    } elseif (in_array($ext, ['txt', 'md', 'log'], true)) {
        [, $body] = fl_split_txt_body($txt);
        $new_txt = fl_build_txt_header($meta) . $body;
    } elseif (in_array($ext, ['html', 'htm'], true)) {
        $new_txt = fl_write_meta_html($txt, $meta);
    } elseif ($ext === 'js') {
        $new_txt = fl_write_meta_js($txt, $meta);
    } elseif ($ext === 'css') {
        $new_txt = fl_write_meta_css($txt, $meta);
    } elseif ($ext === 'json') {
        $new_txt = fl_write_meta_json($txt, $meta);
    } else {
        return false;
    }

    return file_put_contents($file, $new_txt, LOCK_EX) !== false;
}

function fl_write_meta_php(string $txt, array $meta): string
{
    $legacy = 'APP_DESCRIPTION|APP_Description|Description|APP_VERSION|APP_CATEGORY|APP_MEMO';
    $txt = preg_replace(
        "/define\s*\(\s*['\"](?:{$legacy})['\"]\s*,\s*(['\"])(.*?)\1\s*\)\s*;?\s*\n?/s",
        '',
        $txt
    );
    $txt = preg_replace("/\n{3,}/", "\n\n", $txt);

    $insert = '';

    foreach (FL_META_KEYS as $key) {
        if ($meta[$key] !== '') {
            $insert .= "define('{$key}', '" . fl_escape_php_string($meta[$key]) . "');\n";
        }
    }

    if ($insert === '') {
        return $txt;
    }

    if (preg_match('/<\?php/', $txt)) {
        return preg_replace('/<\?php\s*/', "<?php\n" . $insert, $txt, 1);
    }

    return "<?php\n" . $insert . "?>\n" . $txt;
}

function fl_write_meta_html(string $txt, array $meta): string
{
    $txt = preg_replace('/<!--\s*(?:APP_\w+|Description):.*?-->\s*/s', '', $txt);

    $block = '';

    foreach (FL_META_KEYS as $key) {
        if ($meta[$key] !== '') {
            $block .= '<!-- ' . $key . ': ' . htmlspecialchars($meta[$key], ENT_QUOTES, 'UTF-8') . " -->\n";
        }
    }

    if ($block === '') {
        return $txt;
    }

    if (preg_match('/<head>/i', $txt)) {
        return preg_replace('/<head>/i', "<head>\n" . $block, $txt, 1);
    }

    return $block . $txt;
}

function fl_write_meta_js(string $txt, array $meta): string
{
    $txt = preg_replace('/\/\/\s*(?:APP_\w+|Description):.*$/m', '', $txt);
    $prefix = '';

    foreach (FL_META_KEYS as $key) {
        if ($meta[$key] !== '') {
            $prefix .= '// ' . $key . ': ' . $meta[$key] . "\n";
        }
    }

    return $prefix . ltrim($txt, "\n");
}

function fl_write_meta_css(string $txt, array $meta): string
{
    $txt = preg_replace('/\/\*\s*(?:APP_\w+|Description)[\s\S]*?\*\//s', '', $txt);
    $lines = [];

    foreach (FL_META_KEYS as $key) {
        if ($meta[$key] !== '') {
            $lines[] = $key . ': ' . $meta[$key];
        }
    }

    if ($lines === []) {
        return ltrim($txt, "\n");
    }

    return '/* ' . implode("\n", $lines) . " */\n" . ltrim($txt, "\n");
}

function fl_write_meta_json(string $txt, array $meta): string
{
    $data = json_decode($txt, true);

    if (!is_array($data)) {
        $data = [];
    }

    foreach (FL_META_KEYS as $key) {
        if ($meta[$key] !== '') {
            $data[$key] = $meta[$key];
        } else {
            unset($data[$key]);
        }
    }

    unset($data['Description']);

    $encoded = json_encode($data, JSON_UNESCAPED_UNICODE | JSON_PRETTY_PRINT);

    if ($encoded === false) {
        return $txt;
    }

    return $encoded . "\n";
}

function fl_meta_sort_value(string $file, string $sort, array $meta): string
{
    $mtime = @filemtime($file) ?: 0;

    switch ($sort) {
        case 'ver':
            return fl_display_version($meta, basename($file), $mtime);
        case 'category':
            return $meta['APP_CATEGORY'];
        case 'description':
            return $meta['APP_DESCRIPTION'];
        case 'memo':
            return $meta['APP_MEMO'];
        case 'name':
            return basename($file);
        case 'size':
            return (string) (@filesize($file) ?: 0);
        case 'date':
        default:
            return (string) $mtime;
    }
}

function fl_is_meta_editable(string $ext): bool
{
    return in_array(strtolower($ext), ['php', 'txt', 'md', 'log', 'html', 'htm', 'js', 'css', 'json'], true);
}

function fl_sort_file_list(array &$files, $dir, $sort, $order)
{
    usort($files, function ($a, $b) use ($dir, $sort, $order) {
        $fa = $dir . '/' . $a;
        $fb = $dir . '/' . $b;

        if (in_array($sort, ['ver', 'category', 'description', 'memo'], true)) {
            $va = fl_meta_sort_value($fa, $sort, fl_read_meta($fa));
            $vb = fl_meta_sort_value($fb, $sort, fl_read_meta($fb));
            $v = strcasecmp($va, $vb);
        } else {
            switch ($sort) {
                case 'name':
                    $v = strcasecmp($a, $b);
                    break;
                case 'size':
                    $v = (filesize($fa) ?: 0) <=> (filesize($fb) ?: 0);
                    break;
                case 'type':
                    $v = strcasecmp(pathinfo($a, PATHINFO_EXTENSION), pathinfo($b, PATHINFO_EXTENSION));
                    break;
                default:
                    $v = (filemtime($fa) ?: 0) <=> (filemtime($fb) ?: 0);
            }
        }

        if ($v === 0) {
            $v = strcasecmp($a, $b);
        }

        return $order === 'asc' ? $v : -$v;
    });
}
