<?php
/**
 * /api/dashboard/folders.php — folder tree CRUD + per-folder ACLs.
 *
 * GET  ?action=list_tree                     → { ok, tree: [...] }
 * GET  ?action=get_breadcrumb&folder_id=X    → { ok, breadcrumb: [...] }
 * POST ?action=create   body: { parent_id?, name, color?, description? }
 * POST ?action=rename   body: { folder_id, name }
 * POST ?action=recolor  body: { folder_id, color }
 * POST ?action=move     body: { folder_id, parent_id|null }
 * POST ?action=delete   body: { folder_id } (soft delete — docs become Unassigned via SET NULL)
 * POST ?action=set_permission body: { folder_id, min_role?, user_id?, can_read?, can_write?, can_manage? }
 * POST ?action=remove_permission body: { permission_id }
 * GET  ?action=list_permissions&folder_id=X  → { ok, permissions: [...] }
 *
 * Tenant-scoped via dbnToolsEnsureDashboardTenant().
 */

declare(strict_types=1);

require_once dirname(__DIR__, 2) . '/includes/bootstrap.php';

dbnToolsRequireAuth();

try {
    $tenant = dbnToolsEnsureDashboardTenant();
} catch (DbnToolsHttpException $e) {
    dbnToolsError($e->getMessage(), $e->status, $e->errorCode);
}

$clientId    = (int)$tenant['client_id'];
$corpusId    = (int)$tenant['corpus_id'];
$clientUser  = (int)($tenant['client_user_id'] ?? 0);
$tenantRole  = (string)($tenant['role'] ?? 'editor');

$db     = dbnToolsDb();
$method = strtoupper((string)($_SERVER['REQUEST_METHOD'] ?? 'GET'));
$action = (string)($_GET['action'] ?? ($method === 'POST' ? '' : 'list_tree'));

try {
    switch ($action) {
        case 'list_tree':
            dbnToolsRequireMethod('GET');
            respondTree($db, $clientId, $corpusId, $clientUser, $tenantRole);
            break;
        case 'get_breadcrumb':
            dbnToolsRequireMethod('GET');
            $fid = (int)($_GET['folder_id'] ?? 0);
            dbnToolsRespond(['ok' => true, 'breadcrumb' => dbnDmsBreadcrumb($fid ?: null, $clientId)]);
            break;
        case 'create':
            dbnToolsRequireMethod('POST');
            respondCreate($db, $clientId, $corpusId, $clientUser, $tenantRole);
            break;
        case 'rename':
            dbnToolsRequireMethod('POST');
            respondRename($db, $clientId, $clientUser, $tenantRole);
            break;
        case 'recolor':
            dbnToolsRequireMethod('POST');
            respondRecolor($db, $clientId, $clientUser, $tenantRole);
            break;
        case 'move':
            dbnToolsRequireMethod('POST');
            respondMove($db, $clientId, $clientUser, $tenantRole);
            break;
        case 'delete':
            dbnToolsRequireMethod('POST');
            respondDelete($db, $clientId, $clientUser, $tenantRole);
            break;
        case 'list_permissions':
            dbnToolsRequireMethod('GET');
            respondListPermissions($db, $clientId, $clientUser, $tenantRole);
            break;
        case 'set_permission':
            dbnToolsRequireMethod('POST');
            respondSetPermission($db, $clientId, $clientUser, $tenantRole);
            break;
        case 'remove_permission':
            dbnToolsRequireMethod('POST');
            respondRemovePermission($db, $clientId, $clientUser, $tenantRole);
            break;
        default:
            dbnToolsError('Unknown action.', 400, 'unknown_action');
    }
} catch (DbnToolsHttpException $e) {
    dbnToolsError($e->getMessage(), $e->status, $e->errorCode, $e->extra);
} catch (Throwable $e) {
    error_log('[dbn-dms/folders] ' . $e->getMessage());
    dbnToolsError('Folder operation failed.', 500, 'folder_op_failed');
}

function respondTree(PDO $db, int $clientId, int $corpusId, int $userId, string $tenantRole): void
{
    $stmt = $db->prepare(
        "SELECT f.id, f.parent_id, f.name, f.slug, f.color, f.description, f.sort_order, f.created_at,
                COALESCE(c.cnt, 0) AS doc_count
         FROM client_folders f
         LEFT JOIN (
             SELECT folder_id, COUNT(*) AS cnt
             FROM client_documents
             WHERE client_id = ? AND deleted_at IS NULL
             GROUP BY folder_id
         ) c ON c.folder_id = f.id
         WHERE f.client_id = ? AND f.corpus_id = ? AND f.deleted_at IS NULL
         ORDER BY f.sort_order ASC, f.name ASC"
    );
    $stmt->execute([$clientId, $clientId, $corpusId]);
    $rows = $stmt->fetchAll();

    // Filter by read ACL.
    $visible = [];
    foreach ($rows as $row) {
        if (dbnDmsUserCanAccessFolder((int)$row['id'], 'read', $clientId, $userId, $tenantRole)) {
            $visible[(int)$row['id']] = [
                'id'         => (int)$row['id'],
                'parent_id'  => $row['parent_id'] ? (int)$row['parent_id'] : null,
                'name'       => (string)$row['name'],
                'slug'       => (string)$row['slug'],
                'color'      => $row['color'] ?? null,
                'description'=> $row['description'] ?? null,
                'sort_order' => (int)$row['sort_order'],
                'doc_count'  => (int)$row['doc_count'],
                'children'   => [],
            ];
        }
    }
    // Tree assembly.
    $roots = [];
    foreach ($visible as $id => &$node) {
        $pid = $node['parent_id'];
        if ($pid && isset($visible[$pid])) {
            $visible[$pid]['children'][] = &$node;
        } else {
            $roots[] = &$node;
        }
    }
    unset($node);

    // Unassigned bucket count.
    $unassigned = $db->prepare(
        'SELECT COUNT(*) FROM client_documents WHERE client_id = ? AND folder_id IS NULL AND deleted_at IS NULL'
    );
    $unassigned->execute([$clientId]);

    // Trash count.
    $trash = $db->prepare(
        'SELECT COUNT(*) FROM client_documents WHERE client_id = ? AND deleted_at IS NOT NULL'
    );
    $trash->execute([$clientId]);

    dbnToolsRespond([
        'ok'                => true,
        'tree'              => $roots,
        'unassigned_count'  => (int)$unassigned->fetchColumn(),
        'trash_count'       => (int)$trash->fetchColumn(),
        'max_depth'         => DBN_DMS_MAX_FOLDER_DEPTH,
    ]);
}

function respondCreate(PDO $db, int $clientId, int $corpusId, int $userId, string $tenantRole): void
{
    $input    = dbnToolsJsonInput(20_000);
    $name     = trim((string)($input['name'] ?? ''));
    $parentId = isset($input['parent_id']) && $input['parent_id'] !== null && $input['parent_id'] !== ''
        ? (int)$input['parent_id'] : null;
    $color    = trim((string)($input['color'] ?? ''));
    $desc     = trim((string)($input['description'] ?? ''));

    if ($name === '' || mb_strlen($name, 'UTF-8') > 200) {
        dbnToolsError('Folder name is required (1–200 chars).', 422, 'invalid_name');
    }
    if ($color !== '' && !preg_match('/^#[0-9a-fA-F]{6}$/', $color)) {
        dbnToolsError('Color must be a #RRGGBB hex value.', 422, 'invalid_color');
    }
    if (mb_strlen($desc, 'UTF-8') > 1000) {
        dbnToolsError('Description is too long (max 1000 chars).', 422, 'description_too_long');
    }

    $parentDepth = dbnDmsFolderDepth($parentId, $clientId);
    if ($parentDepth + 1 > DBN_DMS_MAX_FOLDER_DEPTH) {
        dbnToolsError("Folder depth limit reached (max " . DBN_DMS_MAX_FOLDER_DEPTH . " levels).", 422, 'depth_exceeded');
    }
    if (!dbnDmsUserCanAccessFolder($parentId, 'manage', $clientId, $userId, $tenantRole)) {
        dbnToolsError('You do not have permission to create folders here.', 403, 'forbidden');
    }
    if ($parentId !== null) {
        $parentCheck = $db->prepare('SELECT id FROM client_folders WHERE id = ? AND client_id = ? AND deleted_at IS NULL');
        $parentCheck->execute([$parentId, $clientId]);
        if (!$parentCheck->fetchColumn()) {
            dbnToolsError('Parent folder not found.', 404, 'parent_not_found');
        }
    }

    $slug = dbnDmsUniqueSlug($db, $clientId, $corpusId, $name);
    $stmt = $db->prepare(
        'INSERT INTO client_folders
            (client_id, corpus_id, parent_id, name, slug, description, color, sort_order, created_by, created_at)
         VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, NOW())'
    );
    $stmt->execute([
        $clientId, $corpusId, $parentId, $name, $slug,
        $desc !== '' ? $desc : null,
        $color !== '' ? $color : null,
        $userId ?: null,
    ]);
    $id = (int)$db->lastInsertId();
    dbnDmsLogAudit($clientId, $userId ?: null, 'folder_create', ['name' => $name, 'parent_id' => $parentId], null, $id);

    dbnToolsRespond(['ok' => true, 'folder_id' => $id, 'slug' => $slug], 201);
}

function respondRename(PDO $db, int $clientId, int $userId, string $tenantRole): void
{
    $input = dbnToolsJsonInput(10_000);
    $fid   = (int)($input['folder_id'] ?? 0);
    $name  = trim((string)($input['name'] ?? ''));
    if ($fid <= 0 || $name === '' || mb_strlen($name, 'UTF-8') > 200) {
        dbnToolsError('folder_id and a valid name (1–200) are required.', 422, 'invalid_input');
    }
    if (!dbnDmsUserCanAccessFolder($fid, 'manage', $clientId, $userId, $tenantRole)) {
        dbnToolsError('You do not have permission to rename this folder.', 403, 'forbidden');
    }
    $stmt = $db->prepare('UPDATE client_folders SET name = ?, updated_at = NOW() WHERE id = ? AND client_id = ?');
    $stmt->execute([$name, $fid, $clientId]);
    dbnDmsLogAudit($clientId, $userId ?: null, 'folder_rename', ['name' => $name], null, $fid);
    dbnToolsRespond(['ok' => true]);
}

function respondRecolor(PDO $db, int $clientId, int $userId, string $tenantRole): void
{
    $input = dbnToolsJsonInput(2_000);
    $fid   = (int)($input['folder_id'] ?? 0);
    $color = trim((string)($input['color'] ?? ''));
    if ($fid <= 0) {
        dbnToolsError('folder_id is required.', 422, 'invalid_input');
    }
    if ($color !== '' && !preg_match('/^#[0-9a-fA-F]{6}$/', $color)) {
        dbnToolsError('Color must be a #RRGGBB hex value.', 422, 'invalid_color');
    }
    if (!dbnDmsUserCanAccessFolder($fid, 'manage', $clientId, $userId, $tenantRole)) {
        dbnToolsError('Forbidden.', 403, 'forbidden');
    }
    $stmt = $db->prepare('UPDATE client_folders SET color = ?, updated_at = NOW() WHERE id = ? AND client_id = ?');
    $stmt->execute([$color !== '' ? $color : null, $fid, $clientId]);
    dbnToolsRespond(['ok' => true]);
}

function respondMove(PDO $db, int $clientId, int $userId, string $tenantRole): void
{
    $input    = dbnToolsJsonInput(5_000);
    $fid      = (int)($input['folder_id'] ?? 0);
    $parentId = isset($input['parent_id']) && $input['parent_id'] !== null && $input['parent_id'] !== ''
        ? (int)$input['parent_id'] : null;
    if ($fid <= 0) {
        dbnToolsError('folder_id is required.', 422, 'invalid_input');
    }
    if ($parentId !== null && $parentId === $fid) {
        dbnToolsError('Folder cannot be its own parent.', 422, 'invalid_parent');
    }
    if (!dbnDmsUserCanAccessFolder($fid, 'manage', $clientId, $userId, $tenantRole)) {
        dbnToolsError('Forbidden on source.', 403, 'forbidden_source');
    }
    if ($parentId !== null && !dbnDmsUserCanAccessFolder($parentId, 'manage', $clientId, $userId, $tenantRole)) {
        dbnToolsError('Forbidden on destination.', 403, 'forbidden_dest');
    }
    // Cycle + depth checks.
    if ($parentId !== null) {
        $chain = dbnDmsFolderChain($parentId, $clientId);
        foreach ($chain as $c) {
            if ((int)$c['id'] === $fid) {
                dbnToolsError('Cannot move a folder into one of its own descendants.', 422, 'invalid_cycle');
            }
        }
        $newDepth = count($chain) + 1; // +1 for the moved folder itself
        $childDepth = dbnDmsSubtreeMaxDepth($db, $fid);
        if ($newDepth + ($childDepth - 1) > DBN_DMS_MAX_FOLDER_DEPTH) {
            dbnToolsError('Move would exceed max folder depth.', 422, 'depth_exceeded');
        }
    }
    $stmt = $db->prepare('UPDATE client_folders SET parent_id = ?, updated_at = NOW() WHERE id = ? AND client_id = ?');
    $stmt->execute([$parentId, $fid, $clientId]);
    dbnDmsLogAudit($clientId, $userId ?: null, 'folder_move', ['parent_id' => $parentId], null, $fid);
    dbnToolsRespond(['ok' => true]);
}

function respondDelete(PDO $db, int $clientId, int $userId, string $tenantRole): void
{
    $input = dbnToolsJsonInput(5_000);
    $fid   = (int)($input['folder_id'] ?? 0);
    if ($fid <= 0) {
        dbnToolsError('folder_id is required.', 422, 'invalid_input');
    }
    if (!dbnDmsUserCanAccessFolder($fid, 'manage', $clientId, $userId, $tenantRole)) {
        dbnToolsError('Forbidden.', 403, 'forbidden');
    }
    // Soft delete folder + cascade soft-delete on descendant folders.
    $db->beginTransaction();
    try {
        $allIds = dbnDmsCollectSubtreeIds($db, $fid, $clientId);
        $allIds[] = $fid;
        $placeholders = implode(',', array_fill(0, count($allIds), '?'));
        $stmt = $db->prepare(
            "UPDATE client_folders SET deleted_at = NOW(), deleted_by = ?
             WHERE client_id = ? AND id IN ({$placeholders})"
        );
        $stmt->execute(array_merge([$userId ?: null, $clientId], $allIds));
        // Documents inside: also soft-delete (they appear in Trash).
        $docStmt = $db->prepare(
            "UPDATE client_documents SET deleted_at = NOW(), deleted_by = ?
             WHERE client_id = ? AND folder_id IN ({$placeholders}) AND deleted_at IS NULL"
        );
        $docStmt->execute(array_merge([$userId ?: null, $clientId], $allIds));
        $db->commit();
    } catch (Throwable $e) {
        $db->rollBack();
        throw $e;
    }
    dbnDmsLogAudit($clientId, $userId ?: null, 'folder_delete', [], null, $fid);
    dbnToolsRespond(['ok' => true]);
}

function respondListPermissions(PDO $db, int $clientId, int $userId, string $tenantRole): void
{
    $fid = (int)($_GET['folder_id'] ?? 0);
    if ($fid <= 0) {
        dbnToolsError('folder_id is required.', 422, 'invalid_input');
    }
    if (!dbnDmsUserCanAccessFolder($fid, 'read', $clientId, $userId, $tenantRole)) {
        dbnToolsError('Forbidden.', 403, 'forbidden');
    }
    $stmt = $db->prepare(
        "SELECT p.id, p.folder_id, p.min_role, p.user_id, p.can_read, p.can_write, p.can_manage,
                p.created_at, u.email AS user_email, u.full_name AS user_name
         FROM client_folder_permissions p
         LEFT JOIN client_users u ON u.id = p.user_id
         WHERE p.folder_id = ? AND p.client_id = ?
         ORDER BY p.id ASC"
    );
    $stmt->execute([$fid, $clientId]);
    dbnToolsRespond(['ok' => true, 'permissions' => $stmt->fetchAll()]);
}

function respondSetPermission(PDO $db, int $clientId, int $userId, string $tenantRole): void
{
    $input    = dbnToolsJsonInput(10_000);
    $fid      = (int)($input['folder_id'] ?? 0);
    $minRole  = trim((string)($input['min_role'] ?? ''));
    $targetUid= isset($input['user_id']) && $input['user_id'] ? (int)$input['user_id'] : null;
    $canRead  = !empty($input['can_read'])  ? 1 : 0;
    $canWrite = !empty($input['can_write']) ? 1 : 0;
    $canManage= !empty($input['can_manage']) ? 1 : 0;

    if ($fid <= 0) {
        dbnToolsError('folder_id is required.', 422, 'invalid_input');
    }
    if (!dbnDmsUserCanAccessFolder($fid, 'manage', $clientId, $userId, $tenantRole)) {
        dbnToolsError('Forbidden.', 403, 'forbidden');
    }
    $validRoles = ['viewer','editor','admin','owner'];
    if ($minRole !== '' && !in_array($minRole, $validRoles, true)) {
        dbnToolsError('Invalid min_role.', 422, 'invalid_role');
    }
    if (($minRole === '' && $targetUid === null) || ($minRole !== '' && $targetUid !== null)) {
        dbnToolsError('Exactly one of min_role or user_id must be set.', 422, 'invalid_grantee');
    }

    // UPSERT on the appropriate unique key.
    if ($minRole !== '') {
        $stmt = $db->prepare(
            'INSERT INTO client_folder_permissions
                (folder_id, client_id, min_role, can_read, can_write, can_manage, created_by, created_at)
             VALUES (?, ?, ?, ?, ?, ?, ?, NOW())
             ON DUPLICATE KEY UPDATE
                can_read = VALUES(can_read),
                can_write = VALUES(can_write),
                can_manage = VALUES(can_manage)'
        );
        $stmt->execute([$fid, $clientId, $minRole, $canRead, $canWrite, $canManage, $userId ?: null]);
    } else {
        $stmt = $db->prepare(
            'INSERT INTO client_folder_permissions
                (folder_id, client_id, user_id, can_read, can_write, can_manage, created_by, created_at)
             VALUES (?, ?, ?, ?, ?, ?, ?, NOW())
             ON DUPLICATE KEY UPDATE
                can_read = VALUES(can_read),
                can_write = VALUES(can_write),
                can_manage = VALUES(can_manage)'
        );
        $stmt->execute([$fid, $clientId, $targetUid, $canRead, $canWrite, $canManage, $userId ?: null]);
    }

    dbnDmsLogAudit($clientId, $userId ?: null, 'folder_acl_set', [
        'min_role' => $minRole ?: null,
        'user_id'  => $targetUid,
        'can_read' => $canRead, 'can_write' => $canWrite, 'can_manage' => $canManage,
    ], null, $fid);

    dbnToolsRespond(['ok' => true]);
}

function respondRemovePermission(PDO $db, int $clientId, int $userId, string $tenantRole): void
{
    $input = dbnToolsJsonInput(2_000);
    $pid   = (int)($input['permission_id'] ?? 0);
    if ($pid <= 0) {
        dbnToolsError('permission_id is required.', 422, 'invalid_input');
    }
    // Look up the folder to ACL-check.
    $row = $db->prepare('SELECT folder_id FROM client_folder_permissions WHERE id = ? AND client_id = ?');
    $row->execute([$pid, $clientId]);
    $fid = (int)$row->fetchColumn();
    if (!$fid) {
        dbnToolsError('Permission not found.', 404, 'not_found');
    }
    if (!dbnDmsUserCanAccessFolder($fid, 'manage', $clientId, $userId, $tenantRole)) {
        dbnToolsError('Forbidden.', 403, 'forbidden');
    }
    $del = $db->prepare('DELETE FROM client_folder_permissions WHERE id = ? AND client_id = ?');
    $del->execute([$pid, $clientId]);
    dbnDmsLogAudit($clientId, $userId ?: null, 'folder_acl_remove', ['permission_id' => $pid], null, $fid);
    dbnToolsRespond(['ok' => true]);
}

function dbnDmsCollectSubtreeIds(PDO $db, int $rootId, int $clientId): array
{
    $collected = [];
    $stack = [$rootId];
    $guard = 0;
    while ($stack && $guard++ < 1000) {
        $batch = $stack;
        $stack = [];
        $placeholders = implode(',', array_fill(0, count($batch), '?'));
        $stmt = $db->prepare(
            "SELECT id FROM client_folders
             WHERE client_id = ? AND parent_id IN ({$placeholders}) AND deleted_at IS NULL"
        );
        $stmt->execute(array_merge([$clientId], $batch));
        foreach ($stmt->fetchAll() as $row) {
            $cid = (int)$row['id'];
            $collected[] = $cid;
            $stack[] = $cid;
        }
    }
    return $collected;
}

function dbnDmsSubtreeMaxDepth(PDO $db, int $rootId): int
{
    // Depth of the subtree starting at rootId, where rootId itself counts as 1.
    $depth = 1;
    $current = [$rootId];
    $guard = 0;
    while ($current && $guard++ < 20) {
        $placeholders = implode(',', array_fill(0, count($current), '?'));
        $stmt = $db->prepare(
            "SELECT id FROM client_folders
             WHERE parent_id IN ({$placeholders}) AND deleted_at IS NULL"
        );
        $stmt->execute($current);
        $next = array_map(fn($r) => (int)$r['id'], $stmt->fetchAll());
        if (!$next) {
            break;
        }
        $current = $next;
        $depth++;
    }
    return $depth;
}

function dbnDmsUniqueSlug(PDO $db, int $clientId, int $corpusId, string $name): string
{
    $base = strtolower(trim($name));
    $base = preg_replace('/[^a-z0-9]+/u', '-', $base) ?: 'folder';
    $base = trim($base, '-');
    if ($base === '') {
        $base = 'folder';
    }
    $base = substr($base, 0, 180);
    $slug = $base;
    $check = $db->prepare('SELECT 1 FROM client_folders WHERE client_id = ? AND corpus_id = ? AND slug = ? LIMIT 1');
    $n = 2;
    while (true) {
        $check->execute([$clientId, $corpusId, $slug]);
        if (!$check->fetchColumn()) {
            return $slug;
        }
        $slug = $base . '-' . $n++;
        if ($n > 999) {
            return $base . '-' . substr(bin2hex(random_bytes(3)), 0, 6);
        }
    }
}
