<?php
// iptv-builder.php
// Single-file IPTV database builder & editor (Option C format)

session_start();

$DB_FILE = __DIR__ . '/iptv.json';

// ---------- Core helpers ----------

function load_db($file) {
    if (file_exists($file)) {
        $data = json_decode(file_get_contents($file), true);
        if (isset($data['channels']) && is_array($data['channels'])) {
            return $data['channels'];
        }
    }
    return [];
}

function save_db($file, $channels) {
    $data = ['channels' => array_values($channels)];
    file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
}

function slugify($str) {
    $str = strtolower(trim($str));
    $str = preg_replace('/[^a-z0-9]+/', '-', $str);
    $str = preg_replace('/-+/', '-', $str);
    return trim($str, '-');
}

function detect_country($url, $filename = '') {
    $hay = strtolower($url . ' ' . $filename);
    if (strpos($hay, '.nz') !== false || strpos($hay, 'mjh.nz') !== false) return 'NZ';
    if (strpos($hay, '.co.uk') !== false || strpos($hay, 'uk-') !== false) return 'UK';
    if (strpos($hay, '.us') !== false || strpos($hay, 'pluto') !== false || strpos($hay, 'tubi') !== false) return 'US';
    return 'XX';
}

function generate_id($name, $country) {
    $slug = slugify($name);
    return strtolower($country) . '-' . $slug;
}

function generate_logo($id, $country) {
    $c = strtolower($country);
    $slug = preg_replace('/^[a-z]{2}-/', '', $id);
    return "https://iptv-org.github.io/iptv/logos/$c/$slug.png";
}

function normalize_channel($raw, $filename = '') {
    $name = $raw['name'] ?? $raw['title'] ?? '';
    $url  = $raw['url'] ?? $raw['location'] ?? '';
    if (!$url) return null;

    $country = $raw['country'] ?? detect_country($url, $filename);
    $id = $raw['id'] ?? generate_id($name ?: $url, $country);
    $slug = $raw['slug'] ?? slugify($name ?: $url);
    $logo = $raw['logo'] ?? $raw['thumb'] ?? generate_logo($id, $country);
    $category = $raw['category'] ?? ($raw['group'] ?? 'TV');
    $tags = $raw['tags'] ?? [];
    $isLive = isset($raw['isLive']) ? (bool)$raw['isLive'] : true;
    $network = $raw['network'] ?? $filename;
    $description = $raw['description'] ?? '';

    return [
        'id'          => $id,
        'name'        => $name ?: $url,
        'slug'        => $slug,
        'url'         => $url,
        'logo'        => $logo,
        'country'     => $country,
        'category'    => $category,
        'tags'        => $tags,
        'isLive'      => $isLive,
        'network'     => $network,
        'description' => $description
    ];
}

function sort_channels(&$channels) {
    usort($channels, function($a, $b) {
        $ca = $a['category'] ?? '';
        $cb = $b['category'] ?? '';
        if ($ca === $cb) {
            return strcasecmp($a['name'], $b['name']);
        }
        return strcasecmp($ca, $cb);
    });
}

function parse_m3u($content, $filename = '') {
    $lines = preg_split('/\r\n|\r|\n/', $content);
    $channels = [];
    $meta = null;

    foreach ($lines as $line) {
        $line = trim($line);
        if ($line === '' || strpos($line, '#EXTM3U') === 0) continue;

        if (strpos($line, '#EXTINF:') === 0) {
            $meta = [
                'name' => '',
                'url'  => '',
                'logo' => null,
                'group'=> null,
                'id'   => null
            ];
            $after = substr($line, 8);
            $parts = explode(',', $after, 2);
            $attrs = $parts[0] ?? '';
            $name  = $parts[1] ?? '';
            $meta['name'] = trim($name);

            if (preg_match_all('/(\w+?)="(.*?)"/', $attrs, $m, PREG_SET_ORDER)) {
                foreach ($m as $match) {
                    $key = strtolower($match[1]);
                    $val = $match[2];
                    if ($key === 'tvg-id') $meta['id'] = $val;
                    elseif ($key === 'tvg-logo') $meta['logo'] = $val;
                    elseif ($key === 'group-title') $meta['group'] = $val;
                }
            }
        } elseif ($line[0] !== '#') {
            if ($meta) {
                $meta['url'] = $line;
                $channels[] = normalize_channel([
                    'id'       => $meta['id'],
                    'name'     => $meta['name'],
                    'url'      => $meta['url'],
                    'logo'     => $meta['logo'],
                    'category' => $meta['group']
                ], $filename);
                $meta = null;
            } else {
                $channels[] = normalize_channel([
                    'name' => $line,
                    'url'  => $line
                ], $filename);
            }
        }
    }
    return array_values(array_filter($channels));
}

function parse_json_content($content, $filename = '') {
    $data = json_decode($content, true);
    $out = [];

    if (!$data) return $out;

    // Option C format
    if (isset($data['channels']) && is_array($data['channels'])) {
        foreach ($data['channels'] as $ch) {
            $norm = normalize_channel($ch, $filename);
            if ($norm) $out[] = $norm;
        }
    }
    // items[] format
    elseif (isset($data['items']) && is_array($data['items'])) {
        $group = $data['category'] ?? null;
        foreach ($data['items'] as $item) {
            $item['category'] = $item['category'] ?? $group;
            $norm = normalize_channel($item, $filename);
            if ($norm) $out[] = $norm;
        }
    }
    // array of channels
    elseif (isset($data[0]) && is_array($data[0])) {
        foreach ($data as $item) {
            $norm = normalize_channel($item, $filename);
            if ($norm) $out[] = $norm;
        }
    }

    return $out;
}

function export_m3u($channels) {
    $lines = ["#EXTM3U"];
    foreach ($channels as $ch) {
        $attrs = [];
        if (!empty($ch['id']))    $attrs[] = 'tvg-id="' . addslashes($ch['id']) . '"';
        if (!empty($ch['logo']))  $attrs[] = 'tvg-logo="' . addslashes($ch['logo']) . '"';
        if (!empty($ch['category'])) $attrs[] = 'group-title="' . addslashes($ch['category']) . '"';
        $name = $ch['name'] ?? $ch['url'];
        $lines[] = '#EXTINF:-1 ' . implode(' ', $attrs) . ',' . $name;
        $lines[] = $ch['url'];
    }
    return implode("\n", $lines);
}

function export_csv($channels) {
    $fh = fopen('php://temp', 'r+');
    fputcsv($fh, ['id','name','slug','url','logo','country','category','tags','isLive','network','description']);
    foreach ($channels as $ch) {
        fputcsv($fh, [
            $ch['id'] ?? '',
            $ch['name'] ?? '',
            $ch['slug'] ?? '',
            $ch['url'] ?? '',
            $ch['logo'] ?? '',
            $ch['country'] ?? '',
            $ch['category'] ?? '',
            implode('|', $ch['tags'] ?? []),
            !empty($ch['isLive']) ? 1 : 0,
            $ch['network'] ?? '',
            $ch['description'] ?? ''
        ]);
    }
    rewind($fh);
    return stream_get_contents($fh);
}

function export_sql($channels, $table = 'iptv_channels') {
    $lines = [];
    $lines[] = "CREATE TABLE IF NOT EXISTS `$table` (";
    $lines[] = "  id VARCHAR(255) PRIMARY KEY,";
    $lines[] = "  name VARCHAR(255),";
    $lines[] = "  slug VARCHAR(255),";
    $lines[] = "  url TEXT,";
    $lines[] = "  logo TEXT,";
    $lines[] = "  country VARCHAR(8),";
    $lines[] = "  category VARCHAR(64),";
    $lines[] = "  tags TEXT,";
    $lines[] = "  isLive TINYINT(1),";
    $lines[] = "  network VARCHAR(255),";
    $lines[] = "  description TEXT";
    $lines[] = ");";
    $lines[] = "";

    foreach ($channels as $ch) {
        $vals = [
            'id'          => $ch['id'] ?? '',
            'name'        => $ch['name'] ?? '',
            'slug'        => $ch['slug'] ?? '',
            'url'         => $ch['url'] ?? '',
            'logo'        => $ch['logo'] ?? '',
            'country'     => $ch['country'] ?? '',
            'category'    => $ch['category'] ?? '',
            'tags'        => implode('|', $ch['tags'] ?? []),
            'isLive'      => !empty($ch['isLive']) ? 1 : 0,
            'network'     => $ch['network'] ?? '',
            'description' => $ch['description'] ?? ''
        ];
        $escaped = array_map(function($v) {
            return "'" . str_replace("'", "''", $v) . "'";
        }, $vals);
        $lines[] = "INSERT INTO `$table` (`id`,`name`,`slug`,`url`,`logo`,`country`,`category`,`tags`,`isLive`,`network`,`description`) VALUES (" . implode(',', $escaped) . ");";
    }
    return implode("\n", $lines);
}

function check_dead_stream($url, $timeout = 3) {
    $ch = curl_init($url);
    curl_setopt_array($ch, [
        CURLOPT_NOBODY => true,
        CURLOPT_RETURNTRANSFER => true,
        CURLOPT_TIMEOUT => $timeout,
        CURLOPT_FOLLOWLOCATION => true
    ]);
    curl_exec($ch);
    $code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
    curl_close($ch);
    return ($code < 200 || $code >= 400);
}

// ---------- API endpoints ----------

if (isset($_GET['api'])) {
    header('Content-Type: application/json; charset=utf-8');
    $action = $_GET['api'];

    if ($action === 'load') {
        $channels = load_db($DB_FILE);
        sort_channels($channels);
        echo json_encode(['channels' => $channels]);
        exit;
    }

    if ($action === 'save' && $_SERVER['REQUEST_METHOD'] === 'POST') {
        $raw = json_decode(file_get_contents('php://input'), true);
        $channels = $raw['channels'] ?? [];
        save_db($DB_FILE, $channels);
        echo json_encode(['status' => 'ok', 'count' => count($channels)]);
        exit;
    }

    if ($action === 'upload' && $_SERVER['REQUEST_METHOD'] === 'POST') {
        $channels = load_db($DB_FILE);
        $new = [];

        if (isset($_FILES['files'])) {
            $files = $_FILES['files'];
            for ($i = 0; $i < count($files['name']); $i++) {
                if ($files['error'][$i] !== UPLOAD_ERR_OK) continue;
                $name = $files['name'][$i];
                $ext  = strtolower(pathinfo($name, PATHINFO_EXTENSION));
                $content = file_get_contents($files['tmp_name'][$i]);

                if (in_array($ext, ['m3u','m3u8'])) {
                    $parsed = parse_m3u($content, $name);
                } elseif ($ext === 'json') {
                    $parsed = parse_json_content($content, $name);
                } else {
                    $parsed = [];
                }
                $new = array_merge($new, $parsed);
            }
        }

        // merge + dedupe by URL
        $all = array_merge($channels, $new);
        $seen = [];
        $merged = [];
        foreach ($all as $ch) {
            if (empty($ch['url'])) continue;
            $key = $ch['url'];
            if (isset($seen[$key])) continue;
            $seen[$key] = true;
            $merged[] = $ch;
        }
        sort_channels($merged);
        save_db($DB_FILE, $merged);

        echo json_encode(['status' => 'ok', 'added' => count($new), 'total' => count($merged)]);
        exit;
    }

    if ($action === 'export') {
        $type = $_GET['type'] ?? 'json';
        $channels = load_db($DB_FILE);
        sort_channels($channels);

        if ($type === 'm3u') {
            echo json_encode(['type' => 'm3u', 'content' => export_m3u($channels)]);
        } elseif ($type === 'csv') {
            echo json_encode(['type' => 'csv', 'content' => export_csv($channels)]);
        } elseif ($type === 'sql') {
            echo json_encode(['type' => 'sql', 'content' => export_sql($channels)]);
        } else {
            echo json_encode(['type' => 'json', 'content' => json_encode(['channels' => $channels], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)]);
        }
        exit;
    }

    if ($action === 'check_dead' && $_SERVER['REQUEST_METHOD'] === 'POST') {
        $channels = load_db($DB_FILE);
        $limit = isset($_GET['limit']) ? (int)$_GET['limit'] : 50;
        $checked = 0;
        $dead = [];

        foreach ($channels as $idx => $ch) {
            if ($checked >= $limit) break;
            $url = $ch['url'] ?? '';
            if (!$url) continue;
            $isDead = check_dead_stream($url);
            if ($isDead) {
                $dead[] = $ch['id'] ?? $url;
                $channels[$idx]['dead'] = true;
            } else {
                $channels[$idx]['dead'] = false;
            }
            $checked++;
        }
        save_db($DB_FILE, $channels);
        echo json_encode(['checked' => $checked, 'dead' => $dead]);
        exit;
    }

    echo json_encode(['error' => 'Unknown action']);
    exit;
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>IPTV JSON Builder & Editor</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
    margin:0;
    font-family:system-ui,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif;
    background:#05060a;
    color:#f5f5f5;
}
header {
    padding:10px 16px;
    background:#10121a;
    display:flex;
    justify-content:space-between;
    align-items:center;
}
header h1 {
    margin:0;
    font-size:18px;
}
main {
    padding:10px 16px 40px;
}
.controls {
    display:flex;
    flex-wrap:wrap;
    gap:8px;
    margin-bottom:10px;
}
button, select {
    border-radius:999px;
    border:none;
    padding:6px 12px;
    font-size:13px;
    cursor:pointer;
}
button.primary {
    background:#e50914;
    color:#fff;
}
button.secondary {
    background:#333;
    color:#fff;
}
#uploadZone {
    border:2px dashed rgba(255,255,255,0.3);
    border-radius:10px;
    padding:16px;
    text-align:center;
    margin-bottom:10px;
}
#uploadZone.dragover {
    background:rgba(255,255,255,0.06);
}
table {
    width:100%;
    border-collapse:collapse;
    font-size:12px;
}
th, td {
    border-bottom:1px solid rgba(255,255,255,0.06);
    padding:4px 6px;
}
th {
    text-align:left;
    background:#10121a;
    position:sticky;
    top:0;
    z-index:1;
}
tbody tr:nth-child(even) {
    background:rgba(255,255,255,0.02);
}
input.inline {
    width:100%;
    border:none;
    background:transparent;
    color:#f5f5f5;
    font-size:12px;
}
input.inline:focus {
    outline:1px solid #e50914;
    background:#000;
}
.badge-dead {
    color:#ff6b6b;
    font-size:11px;
}
#editorContainer {
    max-height:60vh;
    overflow:auto;
    border-radius:10px;
    border:1px solid rgba(255,255,255,0.1);
}
#exportOutput {
    width:100%;
    height:160px;
    margin-top:10px;
    border-radius:8px;
    border:none;
    background:#10121a;
    color:#f5f5f5;
    font-family:monospace;
    font-size:11px;
    padding:6px;
}
</style>
</head>
<body>
<header>
  <h1>IPTV JSON Builder & Editor</h1>
  <div>
    <button class="secondary" id="btnLoad">Reload</button>
    <button class="primary" id="btnSave">Save iptv.json</button>
  </div>
</header>
<main>
  <div id="uploadZone">
    Drop JSON / M3U files here or
    <button class="secondary" id="btnBrowse">Browse</button>
    <input type="file" id="fileInput" multiple style="display:none;" accept=".json,.m3u,.m3u8">
    <div style="font-size:11px;color:#aaa;margin-top:4px;">They will be merged, cleaned, and normalized into the main database.</div>
  </div>

  <div class="controls">
    <button class="secondary" id="btnAdd">Add Channel</button>
    <button class="secondary" id="btnCheckDead">Check Dead (first 50)</button>
    <select id="exportType">
      <option value="json">Export JSON</option>
      <option value="m3u">Export M3U</option>
      <option value="csv">Export CSV</option>
      <option value="sql">Export SQL</option>
    </select>
    <button class="secondary" id="btnExport">Generate Export</button>
  </div>

  <div id="editorContainer">
    <table id="channelTable">
      <thead>
        <tr>
          <th style="width:14%">Name</th>
          <th style="width:18%">URL</th>
          <th style="width:8%">Country</th>
          <th style="width:10%">Category</th>
          <th style="width:10%">Logo</th>
          <th style="width:8%">Network</th>
          <th style="width:6%">Live</th>
          <th style="width:20%">Description</th>
          <th style="width:6%">Actions</th>
        </tr>
      </thead>
      <tbody id="channelBody"></tbody>
    </table>
  </div>

  <textarea id="exportOutput" placeholder="Exported content will appear here..."></textarea>
</main>

<script>
let channels = [];

async function loadDb() {
  const res = await fetch('?api=load');
  const data = await res.json();
  channels = data.channels || [];
  renderTable();
}

function renderTable() {
  const body = document.getElementById('channelBody');
  body.innerHTML = '';
  channels.forEach((ch, idx) => {
    const tr = document.createElement('tr');

    function cellInput(field, width) {
      const td = document.createElement('td');
      const input = document.createElement('input');
      input.className = 'inline';
      input.value = ch[field] ?? '';
      input.style.width = width || '100%';
      input.addEventListener('change', () => {
        ch[field] = input.value;
      });
      td.appendChild(input);
      return td;
    }

    tr.appendChild(cellInput('name'));
    tr.appendChild(cellInput('url'));
    tr.appendChild(cellInput('country', '40px'));
    tr.appendChild(cellInput('category'));
    tr.appendChild(cellInput('logo'));
    tr.appendChild(cellInput('network'));

    const tdLive = document.createElement('td');
    const liveBox = document.createElement('input');
    liveBox.type = 'checkbox';
    liveBox.checked = !!ch.isLive;
    liveBox.addEventListener('change', () => {
      ch.isLive = liveBox.checked;
    });
    tdLive.appendChild(liveBox);
    if (ch.dead) {
      const span = document.createElement('span');
      span.className = 'badge-dead';
      span.textContent = ' dead';
      tdLive.appendChild(span);
    }
    tr.appendChild(tdLive);

    tr.appendChild(cellInput('description'));

    const tdActions = document.createElement('td');
    const btnDel = document.createElement('button');
    btnDel.textContent = '✕';
    btnDel.style.fontSize = '11px';
    btnDel.addEventListener('click', () => {
      channels.splice(idx, 1);
      renderTable();
    });
    tdActions.appendChild(btnDel);
    tr.appendChild(tdActions);

    body.appendChild(tr);
  });
}

document.getElementById('btnLoad').addEventListener('click', loadDb);

document.getElementById('btnSave').addEventListener('click', async () => {
  const res = await fetch('?api=save', {
    method: 'POST',
    body: JSON.stringify({channels}),
    headers: {'Content-Type': 'application/json'}
  });
  const data = await res.json();
  alert('Saved ' + data.count + ' channels to iptv.json');
});

const uploadZone = document.getElementById('uploadZone');
const fileInput = document.getElementById('fileInput');
document.getElementById('btnBrowse').addEventListener('click', () => fileInput.click());

uploadZone.addEventListener('dragover', e => {
  e.preventDefault();
  uploadZone.classList.add('dragover');
});
uploadZone.addEventListener('dragleave', () => uploadZone.classList.remove('dragover'));
uploadZone.addEventListener('drop', e => {
  e.preventDefault();
  uploadZone.classList.remove('dragover');
  handleFiles(e.dataTransfer.files);
});
fileInput.addEventListener('change', () => handleFiles(fileInput.files));

async function handleFiles(fileList) {
  const form = new FormData();
  Array.from(fileList).forEach(f => form.append('files[]', f));
  const res = await fetch('?api=upload', {method:'POST', body:form});
  const data = await res.json();
  await loadDb();
  alert('Merged ' + data.added + ' new channels. Total: ' + data.total);
}

document.getElementById('btnAdd').addEventListener('click', () => {
  channels.push({
    id: '',
    name: 'New Channel',
    slug: '',
    url: '',
    logo: '',
    country: 'XX',
    category: 'TV',
    tags: [],
    isLive: true,
    network: '',
    description: ''
  });
  renderTable();
});

document.getElementById('btnExport').addEventListener('click', async () => {
  const type = document.getElementById('exportType').value;
  const res = await fetch('?api=export&type=' + encodeURIComponent(type));
  const data = await res.json();
  document.getElementById('exportOutput').value = data.content;
});

document.getElementById('btnCheckDead').addEventListener('click', async () => {
  if (!confirm('Check first 50 streams for dead URLs? This may take some seconds.')) return;
  const res = await fetch('?api=check_dead&limit=50', {method:'POST'});
  const data = await res.json();
  alert('Checked ' + data.checked + ' streams. Dead: ' + data.dead.length);
  await loadDb();
});

loadDb();
</script>
</body>
</html>