<?php
header('Content-Type: application/json; charset=utf-8');

$endpoint = $_GET['endpoint'] ?? 'community';
$baseDir = __DIR__;
$dataDir = $baseDir . '/data';
$communityFile = $dataDir . '/community.json';
$logFile = $dataDir . '/global-log.json';
$profilesFile = $dataDir . '/profiles.json';
$submissionsFile = $dataDir . '/submissions.json';
$shareIndexFile = $dataDir . '/share-index.json';
$paypalTransactionsFile = $dataDir . '/paypal-transactions.json';
$paypalConfigFile = $dataDir . '/paypal-config.json';
$profileEntitlementsFile = $dataDir . '/profile-entitlements.json';
$accessFile = $dataDir . '/access-codes.json';
$grantsFile = $dataDir . '/access-grants.json';
$savesDir = $dataDir . '/saves';

if(!is_dir($dataDir)) @mkdir($dataDir, 0755, true);
if(!is_dir($savesDir)) @mkdir($savesDir, 0755, true);

function read_json($file, $fallback){
  if(!file_exists($file)) return $fallback;
  $raw = file_get_contents($file);
  $j = json_decode($raw, true);
  return is_array($j) ? $j : $fallback;
}
function write_json($file, $data){
  $tmp = $file . '.tmp';
  file_put_contents($tmp, json_encode($data, JSON_PRETTY_PRINT|JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE), LOCK_EX);
  rename($tmp, $file);
}
function clean_id($id){ return preg_replace('/[^a-zA-Z0-9_\-]/','', (string)$id); }
function clean_text($s, $max=120){
  $s = trim(preg_replace('/\s+/', ' ', (string)$s));
  if(strlen($s) > $max) $s = substr($s, 0, $max);
  return $s;
}
function public_base(){
  $https = (!empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') || (($_SERVER['SERVER_PORT'] ?? '') == 443);
  $scheme = $https ? 'https' : 'http';
  $host = $_SERVER['HTTP_HOST'] ?? 'localhost';
  $path = rtrim(dirname($_SERVER['SCRIPT_NAME'] ?? ''), '/\\');
  return $scheme . '://' . $host . ($path ? $path : '');
}
function random_id($prefix='sa'){
  return $prefix . '_' . bin2hex(random_bytes(5)) . '_' . substr((string)time(), -5);
}
function community_default(){
  return [
    'schema'=>'stepaway-community-v2',
    'totalCases'=>0,
    'reportedCases'=>0,
    'publishedStories'=>0,
    'totalMoments'=>0,
    'totalPlays'=>0,
    'saved'=>0,
    'partial'=>0,
    'lost'=>0,
    'byCategory'=>[],
    'recent'=>[],
    'milestonesUnlocked'=>[],
    'lastUpdated'=>null
  ];
}
function profiles_default(){ return ['schema'=>'stepaway-profiles-v1','profiles'=>[]]; }
function submissions_default(){ return ['schema'=>'stepaway-submissions-v1','submissions'=>[], 'hashes'=>[], 'rate'=>[]]; }

function access_default(){ return ['schema'=>'stepaway-access-codes-v1','codes'=>[]]; }
function grants_default(){ return ['schema'=>'stepaway-access-grants-v1','grants'=>[]]; }

function paypal_transactions_default(){ return ['schema'=>'stepaway-paypal-transactions-v1','transactions'=>[], 'aliases'=>[]]; }
function normalise_transaction_id($id){ return strtoupper(preg_replace('/[^A-Za-z0-9]/','', (string)$id)); }
function transaction_hash($id){ return hash('sha256', normalise_transaction_id($id)); }
function mask_transaction_id($id){
  $n = normalise_transaction_id($id);
  if(strlen($n) <= 8) return $n;
  return substr($n, 0, 4) . '…' . substr($n, -4);
}
function transaction_access_token($txHash, $profileId){
  return hash('sha256', $txHash . '|' . clean_id($profileId) . '|stepaway-paypal-transaction-v1');
}
function find_transaction_record($inputId){
  global $paypalTransactionsFile;
  $hash = transaction_hash($inputId);
  $db = read_json($paypalTransactionsFile, paypal_transactions_default());
  $canonical = $hash;
  if(isset($db['aliases'][$hash])) $canonical = $db['aliases'][$hash];
  if(!isset($db['transactions'][$canonical])) return [null, null, $db, $hash];
  return [$db['transactions'][$canonical], $canonical, $db, $hash];
}
function upsert_transaction_record($ids, $meta=[]){
  global $paypalTransactionsFile;
  $db = read_json($paypalTransactionsFile, paypal_transactions_default());
  if(!isset($db['transactions']) || !is_array($db['transactions'])) $db['transactions']=[];
  if(!isset($db['aliases']) || !is_array($db['aliases'])) $db['aliases']=[];
  $ids = array_values(array_filter(array_map('normalise_transaction_id', (array)$ids)));
  if(!$ids) return null;
  $canonical = transaction_hash($ids[0]);
  $record = $db['transactions'][$canonical] ?? [
    'schema'=>'stepaway-paypal-transaction-record-v1',
    'createdAt'=>gmdate('c'),
    'activeProfiles'=>[],
    'stats'=>['totalCases'=>0,'saved'=>0,'partial'=>0,'lost'=>0,'plays'=>0]
  ];
  $record['status'] = $meta['status'] ?? ($record['status'] ?? 'completed');
  $record['productType'] = $meta['productType'] ?? ($record['productType'] ?? 'official_deck');
  $defs = product_defaults($record['productType']);
  $record['tier'] = $meta['tier'] ?? ($record['tier'] ?? ($defs['tier'] ?? 'full'));
  $mp = isset($meta['maxProfiles']) ? (int)$meta['maxProfiles'] : 0;
  $record['maxProfiles'] = $mp > 0 ? $mp : (int)($record['maxProfiles'] ?? ($defs['maxProfiles'] ?? 3));
  $record['label'] = $meta['label'] ?? ($record['label'] ?? ($defs['label'] ?? 'Official Deck + Full App'));
  $record['deckOwner'] = (bool)($meta['deckOwner'] ?? ($record['deckOwner'] ?? ($defs['deckOwner'] ?? true)));
  $record['amount'] = $meta['amount'] ?? ($record['amount'] ?? '');
  $record['currency'] = $meta['currency'] ?? ($record['currency'] ?? '');
  $record['email'] = clean_text($meta['email'] ?? ($record['email'] ?? ''), 120);
  $record['source'] = $meta['source'] ?? ($record['source'] ?? 'manual');
  $record['displayId'] = mask_transaction_id($ids[0]);
  $record['updatedAt'] = gmdate('c');
  $record['aliases'] = array_values(array_unique(array_merge($record['aliases'] ?? [], array_map('transaction_hash', $ids))));
  foreach($ids as $id){
    $h = transaction_hash($id);
    $db['aliases'][$h] = $canonical;
  }
  $db['transactions'][$canonical] = $record;
  $db['lastUpdated'] = gmdate('c');
  write_json($paypalTransactionsFile, $db);
  return [$canonical, $record];
}

function profile_entitlements_default(){ return ['schema'=>'stepaway-profile-entitlements-v1','profiles'=>[]]; }
function product_defaults($productType){
  $type = preg_replace('/[^a-z_]/','', (string)$productType);
  if($type === 'app_access_only') return ['productType'=>'app_access_only','tier'=>'full','maxProfiles'=>1,'deckOwner'=>false,'label'=>'App Access Only'];
  if($type === 'case_pack') return ['productType'=>'case_pack','tier'=>'pack','maxProfiles'=>1,'deckOwner'=>false,'label'=>'Case Pack'];
  return ['productType'=>'official_deck','tier'=>'full','maxProfiles'=>3,'deckOwner'=>true,'label'=>'Official Deck + Full App'];
}
function update_profile_entitlement($profileId, $txHash, $record, $token){
  global $profileEntitlementsFile, $profilesFile;
  $profileId = clean_id($profileId);
  $productType = $record['productType'] ?? 'official_deck';
  $defs = product_defaults($productType);
  $ents = read_json($profileEntitlementsFile, profile_entitlements_default());
  if(!isset($ents['profiles'][$profileId])){
    $ents['profiles'][$profileId] = [
      'profileId'=>$profileId,
      'fullApp'=>false,
      'deckOwner'=>false,
      'appAccessOnly'=>false,
      'walletCredits'=>0,
      'transactions'=>[],
      'creditLog'=>[],
      'createdAt'=>gmdate('c')
    ];
  }
  $p =& $ents['profiles'][$profileId];
  $beforeAppOnly = !empty($p['appAccessOnly']);
  $beforeDeck = !empty($p['deckOwner']);
  if(($defs['tier'] ?? '') === 'full') $p['fullApp'] = true;
  if(!empty($defs['deckOwner'])) $p['deckOwner'] = true;
  if($defs['productType'] === 'app_access_only') $p['appAccessOnly'] = true;
  if(!in_array($txHash, $p['transactions'] ?? [], true)) $p['transactions'][] = $txHash;
  $creditAdded = 0;
  if($defs['productType'] === 'official_deck' && $beforeAppOnly && !$beforeDeck && empty($p['deckUpgradeCreditGranted'])){
    $creditAdded = 1;
    $p['walletCredits'] = (int)($p['walletCredits'] ?? 0) + 1;
    $p['deckUpgradeCreditGranted'] = true;
    $p['creditLog'][] = ['type'=>'deck_upgrade_after_app_only','credit'=>1,'transactionHash'=>$txHash,'time'=>gmdate('c')];
  }
  $p['lastToken'] = $token;
  $p['lastUpdated'] = gmdate('c');
  $ents['profiles'][$profileId] = $p;
  write_json($profileEntitlementsFile, $ents);

  $profiles = read_json($profilesFile, profiles_default());
  $prof = $profiles['profiles'][$profileId] ?? ['profileId'=>$profileId,'displayName'=>'Step Away Table','createdAt'=>gmdate('c')];
  $prof['fullApp'] = !empty($p['fullApp']);
  $prof['deckOwner'] = !empty($p['deckOwner']);
  $prof['walletCredits'] = (int)($p['walletCredits'] ?? 0);
  $prof['lastSeen'] = gmdate('c');
  $profiles['profiles'][$profileId] = $prof;
  write_json($profilesFile, $profiles);
  return ['entitlement'=>$p,'creditAdded'=>$creditAdded];
}

function validate_transaction_access(){
  global $grantsFile, $profileEntitlementsFile, $profilesFile;
  $profileId = clean_id($_GET['profileId'] ?? '');
  $token = clean_id($_GET['accessToken'] ?? '');

  if($profileId && $token){
    $grants = read_json($grantsFile, grants_default());
    if(isset($grants['grants'][$token])){
      $g = $grants['grants'][$token];
      if(($g['profileId'] ?? '') === $profileId && ($g['tier'] ?? '') === 'full') return [true, $g];
    }
  }

  if($profileId){
    $ents = read_json($profileEntitlementsFile, profile_entitlements_default());
    if(!empty($ents['profiles'][$profileId]['fullApp'])){
      return [true, ['profileId'=>$profileId,'tier'=>'full','source'=>'admin_profile_entitlement','label'=>'Admin full access']];
    }
    $profiles = read_json($profilesFile, profiles_default());
    if(!empty($profiles['profiles'][$profileId]['fullApp'])){
      return [true, ['profileId'=>$profileId,'tier'=>'full','source'=>'admin_profile_flag','label'=>'Admin full access']];
    }
  }

  return [false, null];
}

function normalise_access_code($code){ return strtoupper(preg_replace('/[^A-Za-z0-9]/','', (string)$code)); }
function access_hash($code){ return hash('sha256', normalise_access_code($code)); }
function access_token($codeHash, $profileId){
  return hash('sha256', $codeHash . '|' . clean_id($profileId) . '|stepaway-access-v1');
}
function has_full_access(){ list($ok,$g)=validate_transaction_access(); return $ok; }
function demo_cases_payload($baseDir){
  $cases = read_json($baseDir . '/data/cases.json', []);
  $demo = array_slice($cases, 0, 1);
  if(isset($demo[0])){
    $demo[0]['demo'] = true;
    $demo[0]['subtitle'] = ($demo[0]['subtitle'] ?? 'Demo Case') . ' · Demo';
  }
  return $demo;
}
function demo_campaigns_payload($baseDir){
  $cases = demo_cases_payload($baseDir);
  $campaignId = $cases[0]['campaign'] ?? '';
  $campaigns = read_json($baseDir . '/data/campaigns.json', []);
  $filtered = array_values(array_filter($campaigns, function($c) use ($campaignId){ return ($c['id'] ?? '') === $campaignId; }));
  if(!$filtered && $campaigns) $filtered = [ $campaigns[0] ];
  if(isset($filtered[0])){
    $filtered[0]['title'] = ($filtered[0]['title'] ?? 'Demo') . ' Demo';
    $filtered[0]['summary'] = 'Poker-deck demo access. Use an official deck code to unlock the full case system.';
  }
  return $filtered;
}
function demo_wrinkles_payload(){ return ['hard'=>[], 'easy'=>[]]; }
function demo_pack_index_payload(){ return ['packs'=>[], 'demo'=>true, 'message'=>'Official deck access unlocks the full pack system.']; }

function share_index_default(){ return ['schema'=>'stepaway-share-index-v1','shares'=>[], 'byHash'=>[]]; }
function input_json(){ return json_decode(file_get_contents('php://input'), true) ?: []; }
function normalise_outcome($outcome){
  $outcome = preg_replace('/[^a-z_]/','', (string)$outcome);
  return in_array($outcome, ['saved','partial','lost'], true) ? $outcome : 'partial';
}
function result_hash($in){
  $basis = [
    'profileId'=>clean_id($in['profileId'] ?? $in['installId'] ?? 'anon'),
    'caseId'=>clean_id($in['caseId'] ?? ''),
    'outcome'=>normalise_outcome($in['outcome'] ?? 'partial'),
    'plays'=>(int)($in['plays'] ?? 0),
    'progress'=>(int)($in['progress'] ?? 0),
    'pressure'=>(int)($in['pressure'] ?? 0),
    'createdAt'=>substr((string)($in['createdAt'] ?? $in['ts'] ?? ''), 0, 16)
  ];
  return hash('sha256', json_encode($basis));
}
function bump_community(&$community, $in, $countCase=true, $published=false){
  $outcome = normalise_outcome($in['outcome'] ?? 'partial');
  $cat = clean_id($in['category'] ?? 'unknown') ?: 'unknown';
  if($countCase){
    $community['totalCases'] = (int)($community['totalCases'] ?? 0) + 1;
    $community['reportedCases'] = (int)($community['reportedCases'] ?? 0) + 1;
    $community[$outcome] = (int)($community[$outcome] ?? 0) + 1;
    $community['totalMoments'] = (int)($community['totalMoments'] ?? 0) + (int)($in['moments'] ?? 0);
    $community['totalPlays'] = (int)($community['totalPlays'] ?? 0) + (int)($in['plays'] ?? 0);
    if(!isset($community['byCategory'][$cat])) $community['byCategory'][$cat] = 0;
    $community['byCategory'][$cat]++;
  }
  if($published) $community['publishedStories'] = (int)($community['publishedStories'] ?? 0) + 1;
  $recent = $community['recent'] ?? [];
  array_unshift($recent, [
    'caseId'=>clean_id($in['caseId'] ?? ''),
    'title'=>clean_text($in['title'] ?? 'Shared case', 90),
    'category'=>$cat,
    'outcome'=>$outcome,
    'published'=>$published,
    'shareId'=>clean_id($in['shareId'] ?? ''),
    'time'=>gmdate('c')
  ]);
  $community['recent'] = array_slice($recent, 0, 20);
  $community['lastUpdated'] = gmdate('c');
}


function public_content_map(){
  return [
    'cards'=>'data/cards.json',
    'cards_manifest'=>'data/cards_manifest.json',
    'cases'=>'data/cases.json',
    'campaigns'=>'data/campaigns.json',
    'wrinkles'=>'data/wrinkles.json',
    'settings'=>'data/settings.json',
    'about'=>'data/about.json',
    'variables'=>'data/variables.json',
    'pack-index'=>'content/packs/index.json'
  ];
}
function send_json_file($path){
  if(!file_exists($path)){ http_response_code(404); echo json_encode(['ok'=>false,'error'=>'missing']); exit; }
  $raw = file_get_contents($path);
  $j = json_decode($raw, true);
  if(json_last_error() !== JSON_ERROR_NONE){ http_response_code(500); echo json_encode(['ok'=>false,'error'=>'invalid_json']); exit; }
  echo json_encode($j, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit;
}



if($endpoint === 'transaction-activate' && $_SERVER['REQUEST_METHOD'] === 'POST'){
  $in = input_json();
  $profileId = clean_id($in['profileId'] ?? '');
  $txId = normalise_transaction_id($in['transactionId'] ?? $in['code'] ?? '');
  if(!$profileId || !$txId){ http_response_code(400); echo json_encode(['ok'=>false,'error'=>'missing_transaction_or_profile']); exit; }

  list($record, $canonical, $db, $enteredHash) = find_transaction_record($txId);
  if(!$record){ http_response_code(403); echo json_encode(['ok'=>false,'error'=>'transaction_not_found']); exit; }
  if(($record['status'] ?? '') !== 'completed'){ http_response_code(403); echo json_encode(['ok'=>false,'error'=>'transaction_not_completed']); exit; }

  $productType = $record['productType'] ?? 'official_deck';
  $defs = product_defaults($productType);
  $max = max(1, (int)($record['maxProfiles'] ?? ($defs['maxProfiles'] ?? 3)));
  $profiles = $record['activeProfiles'] ?? [];
  $now = gmdate('c');
  $already = false;
  foreach($profiles as &$p){ if(($p['profileId'] ?? '') === $profileId){ $p['lastSeen']=$now; $already=true; } }
  unset($p);
  $moved = null;
  if(!$already){
    if(count($profiles) >= $max){
      $moved = array_shift($profiles);
      if($moved && isset($moved['token'])){
        $grants = read_json($grantsFile, grants_default());
        unset($grants['grants'][$moved['token']]);
        write_json($grantsFile, $grants);
      }
    }
    $profiles[] = ['profileId'=>$profileId,'activatedAt'=>$now,'lastSeen'=>$now,'token'=>transaction_access_token($canonical,$profileId)];
  }
  $token = transaction_access_token($canonical,$profileId);
  $record['activeProfiles'] = $profiles;
  $record['lastActivatedAt'] = $now;
  $db['transactions'][$canonical] = $record;
  $db['lastUpdated'] = $now;
  write_json($paypalTransactionsFile, $db);

  $tier = $record['tier'] ?? ($defs['tier'] ?? 'full');
  $label = $record['label'] ?? ($defs['label'] ?? 'PayPal Purchase');
  $grants = read_json($grantsFile, grants_default());
  if($tier === 'full'){
    $grants['grants'][$token] = [
      'profileId'=>$profileId,
      'tier'=>'full',
      'source'=>'paypal_transaction',
      'transactionHash'=>$canonical,
      'productType'=>$productType,
      'label'=>$label . ' ' . ($record['displayId'] ?? ''),
      'issuedAt'=>$now
    ];
    write_json($grantsFile, $grants);
  }

  $ent = update_profile_entitlement($profileId, $canonical, $record, $token);

  echo json_encode([
    'ok'=>true,
    'tier'=>$tier,
    'productType'=>$productType,
    'accessToken'=>$token,
    'label'=>$label . ' ' . ($record['displayId'] ?? ''),
    'maxProfiles'=>$max,
    'activeProfiles'=>count($profiles),
    'movedProfile'=>$moved ? ($moved['profileId'] ?? null) : null,
    'creditAdded'=>$ent['creditAdded'] ?? 0,
    'walletCredits'=>$ent['entitlement']['walletCredits'] ?? 0,
    'deckOwner'=>$ent['entitlement']['deckOwner'] ?? false,
    'appAccessOnly'=>$ent['entitlement']['appAccessOnly'] ?? false,
    'stats'=>$record['stats'] ?? null
  ], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit;
}

if($endpoint === 'transaction-check'){
  list($ok,$grant)=validate_transaction_access();
  $profileId = clean_id($_GET['profileId'] ?? '');
  $ents = read_json($profileEntitlementsFile, profile_entitlements_default());
  $ent = $profileId && isset($ents['profiles'][$profileId]) ? $ents['profiles'][$profileId] : null;
  echo json_encode(['ok'=>true,'full'=>$ok,'tier'=>$ok?'full':'demo','grant'=>$ok?$grant:null,'entitlement'=>$ent], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit;
}

if($endpoint === 'paypal-manual-import' && $_SERVER['REQUEST_METHOD'] === 'POST'){
  $in = input_json();
  $adminKey = (string)($in['adminKey'] ?? '');
  $expected = getenv('STEPAWAY_ADMIN_IMPORT_KEY') ?: '';
  if(!$expected || !hash_equals($expected, $adminKey)){ http_response_code(403); echo json_encode(['ok'=>false,'error'=>'admin_key_required']); exit; }
  $tx = $in['transactionId'] ?? '';
  $order = $in['orderId'] ?? '';
  if(!normalise_transaction_id($tx)){ http_response_code(400); echo json_encode(['ok'=>false,'error'=>'transaction_required']); exit; }
  $ids = [$tx];
  if(normalise_transaction_id($order)) $ids[] = $order;
  list($hash,$record) = upsert_transaction_record($ids, [
    'status'=>'completed',
    'productType'=>$in['productType'] ?? ($in['product_type'] ?? 'official_deck'),
    'tier'=>$in['tier'] ?? 'full',
    'maxProfiles'=>(int)($in['maxProfiles'] ?? ($in['max_profiles'] ?? 0)),
    'amount'=>$in['amount'] ?? '',
    'currency'=>$in['currency'] ?? 'GBP',
    'email'=>$in['email'] ?? '',
    'source'=>'manual_import'
  ]);
  echo json_encode(['ok'=>true,'transactionHash'=>$hash,'displayId'=>$record['displayId'] ?? ''], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit;
}


if($endpoint === 'access-activate' && $_SERVER['REQUEST_METHOD'] === 'POST'){
  $in = input_json();
  $profileId = clean_id($in['profileId'] ?? '');
  $codeRaw = $in['code'] ?? '';
  $codeNorm = normalise_access_code($codeRaw);
  if(!$profileId || !$codeNorm){ http_response_code(400); echo json_encode(['ok'=>false,'error'=>'missing_code_or_profile']); exit; }
  $hash = access_hash($codeNorm);
  $access = read_json($accessFile, access_default());
  if(!isset($access['codes'][$hash])){ http_response_code(403); echo json_encode(['ok'=>false,'error'=>'invalid_code']); exit; }
  $entry = $access['codes'][$hash];
  $acts = $entry['activations'] ?? [];
  $existing = null;
  foreach($acts as $a){ if(($a['profileId'] ?? '') === $profileId){ $existing = $a; break; } }
  $max = (int)($entry['maxActivations'] ?? 1);
  if(!$existing && count($acts) >= $max){ http_response_code(403); echo json_encode(['ok'=>false,'error'=>'activation_limit_reached']); exit; }
  $token = access_token($hash, $profileId);
  if(!$existing){
    $acts[] = ['profileId'=>$profileId,'token'=>$token,'activatedAt'=>gmdate('c'),'lastSeen'=>gmdate('c')];
  }else{
    foreach($acts as &$a){ if(($a['profileId'] ?? '') === $profileId){ $a['lastSeen']=gmdate('c'); $a['token']=$token; } }
  }
  $entry['activations'] = $acts;
  $entry['lastActivatedAt'] = gmdate('c');
  $access['codes'][$hash] = $entry;
  write_json($accessFile, $access);

  $grants = read_json($grantsFile, grants_default());
  $grants['grants'][$token] = [
    'profileId'=>$profileId,
    'tier'=>$entry['tier'] ?? 'full',
    'label'=>$entry['label'] ?? 'Official Deck',
    'codeHash'=>$hash,
    'issuedAt'=>gmdate('c')
  ];
  write_json($grantsFile, $grants);

  echo json_encode(['ok'=>true,'tier'=>$entry['tier'] ?? 'full','label'=>$entry['label'] ?? 'Official Deck','accessToken'=>$token], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit;
}

if($endpoint === 'access-check'){
  $ok = has_full_access();
  echo json_encode(['ok'=>true,'tier'=>$ok?'full':'demo','full'=>$ok], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit;
}


if($endpoint === 'content'){
  $file = clean_id($_GET['file'] ?? '');
  $map = public_content_map();
  $full = has_full_access();
  if(!$full){
    if($file === 'cases'){ echo json_encode(demo_cases_payload($baseDir), JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit; }
    if($file === 'campaigns'){ echo json_encode(demo_campaigns_payload($baseDir), JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit; }
    if($file === 'wrinkles'){ echo json_encode(demo_wrinkles_payload(), JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit; }
    if($file === 'pack-index'){ echo json_encode(demo_pack_index_payload(), JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit; }
    if($file === 'pack'){ http_response_code(403); echo json_encode(['ok'=>false,'error'=>'official_access_required']); exit; }
  }
  if(isset($map[$file])) send_json_file($baseDir . '/' . $map[$file]);
  if($file === 'pack'){
    $name = preg_replace('/[^a-zA-Z0-9_\-.]/','', (string)($_GET['name'] ?? ''));
    if(!$name || $name === 'index.json' || strpos($name, '..') !== false){ http_response_code(400); echo json_encode(['ok'=>false,'error'=>'bad_pack']); exit; }
    send_json_file($baseDir . '/content/packs/' . $name);
  }
  http_response_code(404); echo json_encode(['ok'=>false,'error'=>'unknown_public_content']); exit;
}

if($endpoint === 'profile'){
  if($_SERVER['REQUEST_METHOD'] === 'POST'){
    $in = input_json();
    $profiles = read_json($profilesFile, profiles_default());
    $profileId = clean_id($in['profileId'] ?? '');
    if(!$profileId || !isset($profiles['profiles'][$profileId])) $profileId = random_id('table');
    $displayName = clean_text($in['displayName'] ?? 'Step Away Table', 40) ?: 'Step Away Table';
    $p = $profiles['profiles'][$profileId] ?? [
      'profileId'=>$profileId,
      'createdAt'=>gmdate('c'),
      'totalCases'=>0,
      'publishedStories'=>0
    ];
    $p['displayName'] = $displayName;
    $p['lastSeen'] = gmdate('c');
    $profiles['profiles'][$profileId] = $p;
    write_json($profilesFile, $profiles);
    echo json_encode(['ok'=>true,'profile'=>$p], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit;
  }
  $id = clean_id($_GET['id'] ?? '');
  $profiles = read_json($profilesFile, profiles_default());
  echo json_encode(['ok'=>true,'profile'=>$profiles['profiles'][$id] ?? null], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit;
}

if($endpoint === 'community'){
  $community = read_json($communityFile, community_default());
  $profiles = read_json($profilesFile, profiles_default());
  $community['profileCount'] = count($profiles['profiles'] ?? []);
  echo json_encode($community, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit;
}

if($endpoint === 'report' && $_SERVER['REQUEST_METHOD'] === 'POST'){
  $in = input_json();
  $hash = clean_id($in['resultHash'] ?? '') ?: result_hash($in);
  $profileId = clean_id($in['profileId'] ?? $in['installId'] ?? 'anon');
  $sub = read_json($submissionsFile, submissions_default());
  $already = isset($sub['hashes'][$hash]);

  if(!$already){
    $community = read_json($communityFile, community_default());
    bump_community($community, $in, true, false);
    write_json($communityFile, $community);

    $log = read_json($GLOBALS['logFile'], []);
    $entry = [
      'hash'=>$hash,
      'profileId'=>$profileId,
      'caseId'=>clean_id($in['caseId'] ?? ''),
      'title'=>clean_text($in['title'] ?? '', 90),
      'category'=>clean_id($in['category'] ?? 'unknown'),
      'outcome'=>normalise_outcome($in['outcome'] ?? 'partial'),
      'plays'=>(int)($in['plays'] ?? 0),
      'moments'=>(int)($in['moments'] ?? 0),
      'pressure'=>(int)($in['pressure'] ?? 0),
      'progress'=>(int)($in['progress'] ?? 0),
      'createdAt'=>gmdate('c')
    ];
    array_unshift($log, $entry);
    write_json($GLOBALS['logFile'], array_slice($log, 0, 500));

    $sub['hashes'][$hash] = gmdate('c');
    array_unshift($sub['submissions'], $entry);
    $sub['submissions'] = array_slice($sub['submissions'], 0, 500);

    $profiles = read_json($profilesFile, profiles_default());
    if($profileId && $profileId !== 'anon'){
      $p = $profiles['profiles'][$profileId] ?? ['profileId'=>$profileId, 'displayName'=>'Step Away Table', 'createdAt'=>gmdate('c'), 'totalCases'=>0, 'publishedStories'=>0];
      $p['totalCases'] = (int)($p['totalCases'] ?? 0) + 1;
      $p['lastSeen'] = gmdate('c');
      $profiles['profiles'][$profileId] = $p;
      write_json($profilesFile, $profiles);
    }
    write_json($submissionsFile, $sub);
  }

  echo json_encode(['ok'=>true,'counted'=>!$already,'duplicate'=>$already,'hash'=>$hash], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit;
}

if($endpoint === 'share' && $_SERVER['REQUEST_METHOD'] === 'POST'){
  $in = input_json();
  $hash = clean_id($in['resultHash'] ?? '') ?: result_hash($in);
  $index = read_json($shareIndexFile, share_index_default());

  if(isset($index['byHash'][$hash])){
    $id = $index['byHash'][$hash];
    echo json_encode(['ok'=>true,'duplicate'=>true,'id'=>$id,'shareUrl'=>public_base() . '/share.php?id=' . rawurlencode($id)], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit;
  }

  $id = random_id('case');
  $in['shareId'] = $id;
  $in['resultHash'] = $hash;
  $in['publishedAt'] = gmdate('c');
  $file = $savesDir . '/' . $id . '.json';
  write_json($file, $in);

  $index['shares'][$id] = ['hash'=>$hash, 'title'=>clean_text($in['title'] ?? 'Shared case', 100), 'outcome'=>normalise_outcome($in['outcome'] ?? 'partial'), 'createdAt'=>gmdate('c')];
  $index['byHash'][$hash] = $id;
  write_json($shareIndexFile, $index);

  $community = read_json($communityFile, community_default());
  bump_community($community, $in, false, true);
  write_json($communityFile, $community);

  $profileId = clean_id($in['profileId'] ?? '');
  if($profileId){
    $profiles = read_json($profilesFile, profiles_default());
    $p = $profiles['profiles'][$profileId] ?? ['profileId'=>$profileId, 'displayName'=>'Step Away Table', 'createdAt'=>gmdate('c'), 'totalCases'=>0, 'publishedStories'=>0];
    $p['publishedStories'] = (int)($p['publishedStories'] ?? 0) + 1;
    $p['lastSeen'] = gmdate('c');
    $profiles['profiles'][$profileId] = $p;
    write_json($profilesFile, $profiles);
  }

  echo json_encode(['ok'=>true,'id'=>$id,'shareUrl'=>public_base() . '/share.php?id=' . rawurlencode($id)], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit;
}

if($endpoint === 'outcome' && $_SERVER['REQUEST_METHOD'] === 'POST'){
  $in = input_json();
  echo json_encode(['ok'=>true, 'noted'=>true], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE); exit;
}

http_response_code(404);
echo json_encode(['ok'=>false,'error'=>'Unknown endpoint'], JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE);
