<?php

declare(strict_types=1);

/**
 * Message areas (SPEC §11.4): CRUD + ordering with an ACS cheat-sheet.
 * The mail spool (kind=mail) is plumbing and cannot be edited away; areas
 * holding messages refuse deletion until they are empty.
 */

require __DIR__ . '/_lib.php';
admin_require();
$db = atdt_db();

if (($_SERVER['REQUEST_METHOD'] ?? '') === 'POST') {
    $id = (int)($_POST['id'] ?? 0);
    $area = $id > 0 ? $db->one('SELECT * FROM message_areas WHERE id = ?', [$id]) : null;
    switch ((string)($_POST['action'] ?? '')) {
        case 'add':
        case 'save':
            $tag = strtoupper(trim((string)($_POST['tag'] ?? '')));
            $name = trim((string)($_POST['name'] ?? ''));
            if (!preg_match('/^[A-Z0-9_]{2,12}$/', $tag)) {
                flash_set('err', 'Tag: 2-12 chars, A-Z 0-9 _ only.');
                admin_redirect('areas.php');
            }
            if ($name === '' || mb_strlen($name) > 40) {
                flash_set('err', 'Name: 1-40 characters.');
                admin_redirect('areas.php');
            }
            $dupe = $db->one('SELECT id FROM message_areas WHERE tag = ?', [$tag]);
            if ($dupe !== null && (int)$dupe['id'] !== $id) {
                flash_set('err', "Tag $tag is already taken.");
                admin_redirect('areas.php');
            }
            $catId = (int)($_POST['category_id'] ?? 0);
            if ($catId > 0 && $db->one('SELECT id FROM message_categories WHERE id = ?', [$catId]) === null) {
                $catId = 0;
            }
            $fields = [
                'tag' => $tag,
                'name' => $name,
                'descr' => mb_strimwidth(trim((string)($_POST['descr'] ?? '')), 0, 100),
                'category_id' => $catId,
                'acs_read' => trim((string)($_POST['acs_read'] ?? '')),
                'acs_write' => trim((string)($_POST['acs_write'] ?? '')),
                'listed' => isset($_POST['listed']) ? 1 : 0, // 1.6.5: discovery, not security
            ];
            // The non-blocking nudge (SPEC 7.1): hidden but open is a
            // choice worth double-checking, never a refusal.
            $nudge = $fields['listed'] === 0 && $fields['acs_read'] === ''
                ? ' This area is hidden from the area list but readable by everyone who reaches it.'
                    . ' Set a Read ACS if you meant it to be private.'
                : '';
            if ($area === null) {
                $fields['kind'] = 'local';
                $fields['sort'] = 1 + (int)$db->val("SELECT COALESCE(MAX(sort), 0) FROM message_areas WHERE kind <> 'mail'");
                $db->insert('message_areas', $fields);
                admin_log("Added message area $tag");
                flash_set('ok', "Area $tag added." . $nudge);
            } else {
                if ((string)$area['kind'] === 'mail') {
                    flash_set('err', 'The mail spool is plumbing. Leave it be.');
                    admin_redirect('areas.php');
                }
                $db->q(
                    'UPDATE message_areas SET tag = ?, name = ?, descr = ?, category_id = ?, acs_read = ?, acs_write = ?, listed = ? WHERE id = ?',
                    [$fields['tag'], $fields['name'], $fields['descr'], $fields['category_id'], $fields['acs_read'], $fields['acs_write'], $fields['listed'], $id]
                );
                if ((string)$area['tag'] !== $tag) {
                    // A rename follows through every reference (1.6.5,
                    // owner ruling): menu targets, defaults, scope rows.
                    \Atdt\AreaScope::renameTag($db, (string)$area['tag'], $tag);
                    admin_log("Renamed message area {$area['tag']} to $tag (references follow)");
                }
                admin_log("Edited message area $tag");
                flash_set('ok', "Area $tag saved." . $nudge);
            }
            admin_redirect('areas.php');

        case 'move':
            // Reorder within the same category: that is the order callers see.
            if ($area !== null && (string)$area['kind'] !== 'mail') {
                $dirn = (string)($_POST['dir'] ?? '') === 'up' ? -1 : 1;
                $swap = $db->one(
                    'SELECT * FROM message_areas WHERE kind <> ? AND category_id = ? AND sort ' . ($dirn < 0 ? '<' : '>') . ' ?
                     ORDER BY sort ' . ($dirn < 0 ? 'DESC' : 'ASC') . ', id ' . ($dirn < 0 ? 'DESC' : 'ASC') . ' LIMIT 1',
                    ['mail', (int)$area['category_id'], (int)$area['sort']]
                );
                if ($swap !== null) {
                    $db->q('UPDATE message_areas SET sort = ? WHERE id = ?', [(int)$swap['sort'], $id]);
                    $db->q('UPDATE message_areas SET sort = ? WHERE id = ?', [(int)$area['sort'], (int)$swap['id']]);
                }
            }
            admin_redirect('areas.php');

        case 'cat_add':
            $cname = trim((string)($_POST['cat_name'] ?? ''));
            if ($cname === '' || mb_strlen($cname) > 30) {
                flash_set('err', 'Category name: 1-30 characters.');
                admin_redirect('areas.php');
            }
            $sort = 10 + (int)$db->val('SELECT COALESCE(MAX(sort), 0) FROM message_categories');
            $db->insert('message_categories', ['name' => $cname, 'sort' => $sort]);
            admin_log("Added area category \"$cname\"");
            flash_set('ok', "Category \"$cname\" added. Assign areas to it below.");
            admin_redirect('areas.php');

        case 'cat_save':
            $cat = $db->one('SELECT * FROM message_categories WHERE id = ?', [$id]);
            $cname = trim((string)($_POST['cat_name'] ?? ''));
            if ($cat === null || $cname === '' || mb_strlen($cname) > 30) {
                flash_set('err', 'Category name: 1-30 characters.');
                admin_redirect('areas.php');
            }
            $db->q('UPDATE message_categories SET name = ? WHERE id = ?', [$cname, $id]);
            admin_log("Renamed area category to \"$cname\"");
            flash_set('ok', 'Category renamed.');
            admin_redirect('areas.php');

        case 'cat_move':
            $cat = $db->one('SELECT * FROM message_categories WHERE id = ?', [$id]);
            if ($cat !== null) {
                $dirn = (string)($_POST['dir'] ?? '') === 'up' ? -1 : 1;
                $swap = $db->one(
                    'SELECT * FROM message_categories WHERE sort ' . ($dirn < 0 ? '<' : '>') . ' ?
                     ORDER BY sort ' . ($dirn < 0 ? 'DESC' : 'ASC') . ', id ' . ($dirn < 0 ? 'DESC' : 'ASC') . ' LIMIT 1',
                    [(int)$cat['sort']]
                );
                if ($swap !== null) {
                    $db->q('UPDATE message_categories SET sort = ? WHERE id = ?', [(int)$swap['sort'], $id]);
                    $db->q('UPDATE message_categories SET sort = ? WHERE id = ?', [(int)$cat['sort'], (int)$swap['id']]);
                }
            }
            admin_redirect('areas.php');

        case 'cat_delete':
            $cat = $db->one('SELECT * FROM message_categories WHERE id = ?', [$id]);
            if ($cat !== null) {
                $db->q('UPDATE message_areas SET category_id = 0 WHERE category_id = ?', [$id]);
                $db->q('DELETE FROM message_categories WHERE id = ?', [$id]);
                admin_log("Deleted area category \"{$cat['name']}\"");
                flash_set('ok', "Category \"{$cat['name']}\" deleted. Its areas are loose again; nothing else was touched.");
            }
            admin_redirect('areas.php');

        case 'delete':
            // 1.6.5 (SPEC 7.4): a targeted area cannot be deleted out
            // from under the menus that name it. Same shape as the
            // holding-messages refusal below.
            if ($area !== null) {
                $refs = \Atdt\AreaScope::referencesToTag($db, (string)$area['tag']);
                if ($refs !== []) {
                    $where = implode(', ', array_map(
                        fn(array $r) => $r['kind'] === 'default'
                            ? "menu {$r['menu']} (default area)"
                            : "menu {$r['menu']} key {$r['hotkey']}",
                        array_slice($refs, 0, 6)
                    )) . (count($refs) > 6 ? ', and ' . (count($refs) - 6) . ' more' : '');
                    flash_set('err', "{$area['tag']} is named by $where. Clear those first.");
                    admin_redirect('areas.php');
                }
            }
            if ($area === null || (string)$area['kind'] === 'mail') {
                flash_set('err', 'That one stays.');
                admin_redirect('areas.php');
            }
            $count = (int)$db->val('SELECT COUNT(*) FROM messages WHERE area_id = ? AND deleted = 0', [$id]);
            if ($count > 0) {
                flash_set('err', "{$area['tag']} still holds $count messages. Empty it first.");
                admin_redirect('areas.php');
            }
            $db->q('DELETE FROM message_areas WHERE id = ?', [$id]);
            admin_log("Deleted message area {$area['tag']}");
            flash_set('ok', "Area {$area['tag']} deleted.");
            admin_redirect('areas.php');
    }
    admin_redirect('areas.php');
}

$cats = $db->all('SELECT * FROM message_categories ORDER BY sort, id');
$areas = $db->all(
    "SELECT a.*, c.name AS cat_name FROM message_areas a
     LEFT JOIN message_categories c ON c.id = a.category_id
     WHERE a.kind <> 'mail'
     ORDER BY COALESCE(c.sort, 2147483647), COALESCE(c.id, 2147483647), a.sort, a.id"
);
$counts = [];
foreach ($db->all('SELECT area_id, COUNT(*) AS n FROM messages WHERE deleted = 0 GROUP BY area_id') as $r) {
    $counts[(int)$r['area_id']] = (int)$r['n'];
}
$catAreaCounts = [];
foreach ($areas as $a) {
    $catAreaCounts[(int)$a['category_id']] = 1 + ($catAreaCounts[(int)$a['category_id']] ?? 0);
}
$edit = isset($_GET['edit']) ? $db->one("SELECT * FROM message_areas WHERE id = ? AND kind <> 'mail'", [(int)$_GET['edit']]) : null;

admin_page_start('Message areas', 'areas');
?>

<div class="cols">
<div>
  <div class="card">
    <table class="list">
      <tr><th></th><th>Tag</th><th>Name</th><th>Msgs</th><th>Read ACS</th><th>Write ACS</th><th></th></tr>
      <?php $prevCat = false; foreach ($areas as $i => $a): ?>
      <?php $catLabel = trim((string)($a['cat_name'] ?? '')); ?>
      <?php if ($cats !== [] && $catLabel !== $prevCat): $prevCat = $catLabel; ?>
      <tr><td colspan="7" class="hint" style="padding-top:.6em"><strong><?= h($catLabel !== '' ? $catLabel : 'Other Boards') ?></strong></td></tr>
      <?php endif; ?>
      <tr>
        <td class="nowrap">
          <form method="post" class="inline"><?= csrf_field() ?>
            <input type="hidden" name="action" value="move"><input type="hidden" name="id" value="<?= (int)$a['id'] ?>">
            <input type="hidden" name="dir" value="up">
            <button class="btn small secondary" title="Move up within its category">↑</button></form>
          <form method="post" class="inline"><?= csrf_field() ?>
            <input type="hidden" name="action" value="move"><input type="hidden" name="id" value="<?= (int)$a['id'] ?>">
            <input type="hidden" name="dir" value="down">
            <button class="btn small secondary" title="Move down within its category">↓</button></form>
        </td>
        <td><code><?= h((string)$a['tag']) ?></code><?= (int)($a['listed'] ?? 1) === 0 ? ' <span class="hint">unlisted</span>' : '' ?></td>
        <td><?= h((string)$a['name']) ?><div class="hint"><?= h((string)$a['descr']) ?></div></td>
        <td><?= $counts[(int)$a['id']] ?? 0 ?></td>
        <td><code><?= h((string)$a['acs_read']) ?: '<span class="hint">open</span>' ?></code></td>
        <td><code><?= h((string)$a['acs_write']) ?: '<span class="hint">open</span>' ?></code></td>
        <td class="nowrap">
          <a href="areas.php?edit=<?= (int)$a['id'] ?>">edit</a>
          <form method="post" class="inline" onsubmit="return confirm('Delete area <?= h((string)$a['tag']) ?>?');">
            <?= csrf_field() ?>
            <input type="hidden" name="action" value="delete"><input type="hidden" name="id" value="<?= (int)$a['id'] ?>">
            <button class="btn small danger">×</button></form>
        </td>
      </tr>
      <?php endforeach; ?>
    </table>
  </div>

  <div class="card">
    <h2>Categories</h2>
    <p class="hint">Shelves for the area list: callers see each category as a
    header with its areas beneath, in this order. Areas without a category
    gather under "Other Boards" at the end. Deleting a category never touches
    the areas in it; they just come loose.</p>
    <table class="list">
      <tr><th></th><th>Name</th><th>Areas</th><th></th></tr>
      <?php foreach ($cats as $ct): ?>
      <tr>
        <td class="nowrap">
          <form method="post" class="inline"><?= csrf_field() ?>
            <input type="hidden" name="action" value="cat_move"><input type="hidden" name="id" value="<?= (int)$ct['id'] ?>">
            <input type="hidden" name="dir" value="up"><button class="btn small secondary">↑</button></form>
          <form method="post" class="inline"><?= csrf_field() ?>
            <input type="hidden" name="action" value="cat_move"><input type="hidden" name="id" value="<?= (int)$ct['id'] ?>">
            <input type="hidden" name="dir" value="down"><button class="btn small secondary">↓</button></form>
        </td>
        <td>
          <form method="post" class="inline"><?= csrf_field() ?>
            <input type="hidden" name="action" value="cat_save"><input type="hidden" name="id" value="<?= (int)$ct['id'] ?>">
            <input type="text" name="cat_name" maxlength="30" value="<?= h((string)$ct['name']) ?>">
            <button class="btn small secondary">Rename</button></form>
        </td>
        <td><?= $catAreaCounts[(int)$ct['id']] ?? 0 ?></td>
        <td>
          <form method="post" class="inline" onsubmit="return confirm('Delete category <?= h((string)$ct['name']) ?>? Its areas stay, uncategorized.');">
            <?= csrf_field() ?>
            <input type="hidden" name="action" value="cat_delete"><input type="hidden" name="id" value="<?= (int)$ct['id'] ?>">
            <button class="btn small danger">×</button></form>
        </td>
      </tr>
      <?php endforeach; ?>
      <?php if ($cats === []): ?>
      <tr><td colspan="4" class="hint">No categories yet. The area list shows flat until you add one.</td></tr>
      <?php endif; ?>
    </table>
    <form method="post" style="margin-top:.7em">
      <?= csrf_field() ?>
      <input type="hidden" name="action" value="cat_add">
      <input type="text" name="cat_name" maxlength="30" placeholder="New category name">
      <button class="btn small">Add category</button>
    </form>
  </div>
</div>

<div>
  <div class="card drawer">
    <h2><?= $edit === null ? 'Add an area' : 'Edit: ' . h((string)$edit['tag']) ?></h2>
    <form method="post">
      <?= csrf_field() ?>
      <input type="hidden" name="action" value="<?= $edit === null ? 'add' : 'save' ?>">
      <?php if ($edit !== null): ?><input type="hidden" name="id" value="<?= (int)$edit['id'] ?>"><?php endif; ?>
      <label>Tag <span class="hint">(short, unique, A-Z 0-9 _)</span>
        <input type="text" name="tag" maxlength="12" value="<?= h((string)($edit['tag'] ?? '')) ?>"></label>
      <label>Name
        <input type="text" name="name" maxlength="40" value="<?= h((string)($edit['name'] ?? '')) ?>"></label>
      <label>Description
        <input type="text" name="descr" maxlength="100" value="<?= h((string)($edit['descr'] ?? '')) ?>"></label>
      <label>Category <span class="hint">(the shelf it lists under)</span>
        <select name="category_id">
          <option value="0">none (Other Boards)</option>
          <?php foreach ($cats as $ct): ?>
          <option value="<?= (int)$ct['id'] ?>"<?= (int)($edit['category_id'] ?? 0) === (int)$ct['id'] ? ' selected' : '' ?>><?= h((string)$ct['name']) ?></option>
          <?php endforeach; ?>
        </select></label>
      <label>Read ACS <span class="hint">(blank = everyone)</span>
        <input type="text" name="acs_read" value="<?= h((string)($edit['acs_read'] ?? '')) ?>"></label>
      <label>Write ACS <span class="hint">(blank = everyone)</span>
        <input type="text" name="acs_write" value="<?= h((string)($edit['acs_write'] ?? '')) ?>"></label>
      <label class="radio"><input type="checkbox" name="listed" <?= (int)($edit['listed'] ?? 1) === 1 ? 'checked' : '' ?>>
        Listed</label>
      <p class="hint">Uncheck to keep this area out of the general
      area-change list. Callers can still reach it from a menu item that
      names it. This is not a privacy setting; use Read ACS for that.</p>
      <button class="btn"><?= $edit === null ? 'Add area' : 'Save area' ?></button>
      <?php if ($edit !== null): ?><a class="btn secondary" href="areas.php">Close</a><?php endif; ?>
    </form>
  </div>

  <aside class="cheat">
    <strong>Access strings (ACS): who can read or write here</strong>
    <p>Leave a field blank and everyone gets in. Otherwise combine the
    tokens below; a caller must satisfy the whole string. You (level 255)
    always pass everything.</p>
    <table>
      <tr><td><code>s20</code></td><td>security level 20 or higher. New callers start at level 10; you are 255. Set levels on the Users page.</td></tr>
      <tr><td><code>fA</code></td><td>has access flag A. Flags are the A-Z checkboxes in each caller's edit drawer. Hand them out like club cards.</td></tr>
      <tr><td><code>v</code></td><td>validated: an approved account, not a pending application.</td></tr>
      <tr><td><code>!fZ</code></td><td><code>!</code> means NOT: this passes only callers <em>without</em> flag Z.</td></tr>
      <tr><td><code>s50fB</code></td><td>tokens side by side mean AND: level 50+ <em>and</em> flag B.</td></tr>
      <tr><td><code>s50|fB</code></td><td><code>|</code> means OR: level 50+ <em>or</em> flag B.</td></tr>
      <tr><td><code>(s50|fB)!fZ</code></td><td>parentheses group: (level 50+ or flag B) and not flag Z.</td></tr>
    </table>
    <p>Common recipes: <code>v</code> = members only · <code>s100</code> = co-SysOps
    · <code>fP</code> = whoever you gave the P flag.</p>
  </aside>
</div>
</div>

<?php admin_page_end(); ?>
