<?php

function cmsNormalizeRedirectSource($value)
{
    $path = parse_url(trim((string) $value), PHP_URL_PATH);
    $path = $path === false || $path === null ? '' : rawurldecode($path);
    $path = '/' . ltrim(preg_replace('#/+#', '/', str_replace('\\', '/', $path)), '/');
    if (strlen($path) > 500 || preg_match('/[\x00-\x1F\x7F]/', $path) || strpos($path, '..') !== false) { return ''; }
    return $path === '' ? '/' : $path;
}

function cmsNormalizeRedirectTarget($value)
{
    $value = trim((string) $value);
    if ($value === '' || preg_match('/[\x00-\x1F\x7F]/', $value)) { return ''; }
    $parts = parse_url($value);
    if ($parts === false || isset($parts['scheme']) || isset($parts['host']) || strpos($value, '//') === 0) { return ''; }
    $path = cmsNormalizeRedirectSource($parts['path'] ?? '');
    if ($path === '') { return ''; }
    return $path . (isset($parts['query']) && $parts['query'] !== '' ? '?' . $parts['query'] : '');
}

function cmsEnsureRedirectSchema($db)
{
    return true;
}

function cmsRedirectGraphHasCycle(array $rules, $source, $target)
{
    $graph=[]; foreach($rules as $rule){if(!empty($rule['is_enabled']))$graph[cmsNormalizeRedirectSource($rule['source_path'])]=cmsNormalizeRedirectSource($rule['target_path']);}
    $graph[cmsNormalizeRedirectSource($source)]=cmsNormalizeRedirectSource($target);
    foreach(array_keys($graph) as $start){$seen=[];$node=$start;while(isset($graph[$node])){if(isset($seen[$node]))return true;$seen[$node]=true;$node=$graph[$node];}}
    return false;
}

function cmsFindRedirect($db, $requestUri)
{
    cmsEnsureRedirectSchema($db); $source=cmsNormalizeRedirectSource($requestUri); if($source==='')return null;
    $rule=$db->fetch("SELECT * FROM {$db->table('redirect_rule')} WHERE source_path=? AND is_enabled=1 LIMIT 1",[$source]);
    if(!$rule)return null;
    $target=cmsNormalizeRedirectTarget($rule['target_path']); if($target===''||cmsNormalizeRedirectSource($target)===$source)return null;
    $db->query("UPDATE {$db->table('redirect_rule')} SET hit_count=hit_count+1,last_hit_at=? WHERE id=?",[time(),$rule['id']]);
    return ['target'=>$target,'status_code'=>intval($rule['status_code'])===302?302:301,'id'=>intval($rule['id'])];
}

function cmsRecordNotFound($db, $requestUri, array $server = [])
{
    cmsEnsureRedirectSchema($db); $path=cmsNormalizeRedirectSource($requestUri); if($path===''||strpos($path,'/admin')===0)return false;
    $query=(string)(parse_url($requestUri,PHP_URL_QUERY)?:'');$now=time();
    $existing=$db->fetch("SELECT id FROM {$db->table('not_found_log')} WHERE request_path=? LIMIT 1",[$path]);
    $data=[substr($query,0,500),$now,substr((string)($server['REMOTE_ADDR']??''),0,50),substr((string)($server['HTTP_REFERER']??''),0,500),substr((string)($server['HTTP_USER_AGENT']??''),0,255)];
    if($existing){$db->query("UPDATE {$db->table('not_found_log')} SET query_string=?,hit_count=hit_count+1,last_seen_at=?,last_ip=?,last_referrer=?,last_user_agent=? WHERE id=?",array_merge($data,[$existing['id']]));}
    else{$db->query("INSERT INTO {$db->table('not_found_log')} (request_path,query_string,hit_count,first_seen_at,last_seen_at,last_ip,last_referrer,last_user_agent) VALUES (?,?,1,?,?,?,?,?)",array_merge([$path,$query,$now],array_slice($data,1)));}
    return true;
}

function cmsFrontendRouteExists($db, $module, $id, $channelDir = '')
{
    if(in_array($module,['index','search','guestbook','cart'],true))return true;
    if($module==='channel'){
        if($id>0)return(bool)$db->fetch("SELECT id FROM {$db->table('channel')} WHERE id=? AND is_show=1 LIMIT 1",[$id]);
        return $channelDir!==''&&(bool)$db->fetch("SELECT id FROM {$db->table('channel')} WHERE channel_dir=? AND is_show=1 LIMIT 1",[$channelDir]);
    }
    $types=['article','product','page','image','download','video','recruit']; if(!in_array($module,$types,true))return false;
    if($id<=0)return true;
    $columns=array_column($db->fetchAll("SHOW COLUMNS FROM {$db->table($module)}"),'Field');$where='id=?';if(in_array('status',$columns,true))$where.=' AND status=1';if(in_array('deleted_at',$columns,true))$where.=' AND deleted_at=0';
    return(bool)$db->fetch("SELECT id FROM {$db->table($module)} WHERE {$where} LIMIT 1",[$id]);
}
