<?php
/**
 * /api/dashboard/documents.php — CRUD for the current tenant's documents.
 *
 * GET  ?action=list   &offset=&limit=&q=&status=&category=&source_type=
 *                     &folder_id=(int|'unassigned'|'all')&include_subfolders=1
 *                     &trashed=0|1&sort=updated_at|title|file_size_bytes&dir=asc|desc
 *      → { ok, total, documents: [...], folder?: {...} }
 *
 * GET  ?action=get&id=123
 *      → { ok, document: {...}, chunks: [...], versions: [...], permissions: {...} }
 *
 * POST ?action=update  body: { id, title?, category?, tags?, language?, author?, folder_id? }
 *      → { ok, document: {...} }
 *
 * POST ?action=delete  body: { ids: [1,2,3], hard_delete?: false }
 *      → { ok, deleted: N }
 *
 * POST ?action=restore body: { ids: [1,2,3] }
 *      → { ok, restored: N }
 *
 * All tenant-isolated 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'];
$userId     = (int)($tenant['client_user_id'] ?? 0);
$tenantRole = (string)($tenant['role'] ?? 'editor');

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

$db = dbnToolsDb();

try {
    switch ($action) {
        case 'list':
            dbnToolsRequireMethod('GET');
            respondList($db, $clientId, $userId, $tenantRole);
            break;
        case 'get':
            dbnToolsRequireMethod('GET');
            respondGet($db, $clientId, $userId, $tenantRole);
            break;
        case 'update':
            dbnToolsRequireMethod('POST');
            respondUpdate($db, $clientId, $userId, $tenantRole);
            break;
        case 'delete':
            dbnToolsRequireMethod('POST');
            respondDelete($db, $clientId, $userId, $tenantRole);
            break;
        case 'restore':
            dbnToolsRequireMethod('POST');
            respondRestore($db, $clientId, $userId, $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/documents] ' . $e->getMessage());
    dbnToolsError('Document operation failed.', 500, 'doc_op_failed');
}

function respondList(PDO $db, int $clientId, int $userId, string $tenantRole): void
{
    $offset = max(0, (int)($_GET['offset'] ?? 0));
    $limit  = max(1, min(200, (int)($_GET['limit'] ?? 25)));
    $q      = trim((string)($_GET['q'] ?? ''));
    $status = trim((string)($_GET['status'] ?? ''));
    $category = trim((string)($_GET['category'] ?? ''));
    $sourceType = trim((string)($_GET['source_type'] ?? ''));
    $trashed = !empty($_GET['trashed']);
    $folderParam = (string)($_GET['folder_id'] ?? 'all');
    $includeSub  = !empty($_GET['include_subfolders']);
    $sort   = strtolower((string)($_GET['sort'] ?? 'updated_at'));
    $dir    = strtolower((string)($_GET['dir'] ?? 'desc')) === 'asc' ? 'ASC' : 'DESC';

    $where  = ['client_id = ?'];
    $params = [$clientId];

    if ($trashed) {
        $where[] = 'deleted_at IS NOT NULL';
    } else {
        $where[] = 'deleted_at IS NULL';
    }

    // Folder scoping
    $folderMeta = null;
    if ($folderParam === 'unassigned') {
        $where[] = 'folder_id IS NULL';
    } elseif ($folderParam === 'all' || $folderParam === '') {
        // no folder filter
    } else {
        $fid = (int)$folderParam;
        if ($fid > 0) {
            if (!dbnDmsUserCanAccessFolder($fid, 'read', $clientId, $userId, $tenantRole)) {
                dbnToolsError('Forbidden.', 403, 'forbidden');
            }
            if ($includeSub) {
                $ids = array_merge([$fid], dbnDmsCollectSubtreeIdsForList($db, $fid, $clientId));
                $ph  = implode(',', array_fill(0, count($ids), '?'));
                $where[] = "folder_id IN ({$ph})";
                $params = array_merge($params, $ids);
            } else {
                $where[]  = 'folder_id = ?';
                $params[] = $fid;
            }
            $folderRow = $db->prepare('SELECT id, name, parent_id, color FROM client_folders WHERE id = ? AND client_id = ?');
            $folderRow->execute([$fid, $clientId]);
            $folderMeta = $folderRow->fetch() ?: null;
        }
    }

    if ($q !== '') {
        $where[] = '(title LIKE ? OR tags LIKE ?)';
        $like = '%' . str_replace(['%','_'], ['\%','\_'], $q) . '%';
        $params[] = $like;
        $params[] = $like;
    }
    if ($status !== '' && in_array($status, ['pending','processing','ready','error'], true)) {
        $where[]  = 'status = ?';
        $params[] = $status;
    }
    if ($category !== '') {
        $where[]  = 'category = ?';
        $params[] = $category;
    }
    if ($sourceType !== '' && in_array($sourceType, ['text','audio','url','tool-output','upload','pdf','docx'], true)) {
        $where[]  = 'source_type = ?';
        $params[] = $sourceType;
    }

    $whereSql = 'WHERE ' . implode(' AND ', $where);

    $sortMap = [
        'updated_at'      => 'COALESCE(updated_at, created_at)',
        'created_at'      => 'created_at',
        'title'           => 'title',
        'file_size_bytes' => 'file_size_bytes',
        'word_count'      => 'word_count',
    ];
    $sortCol = $sortMap[$sort] ?? 'COALESCE(updated_at, created_at)';

    $countStmt = $db->prepare("SELECT COUNT(*) FROM client_documents {$whereSql}");
    $countStmt->execute($params);
    $total = (int)$countStmt->fetchColumn();

    $sql = "SELECT id, folder_id, title, source_type, language, category, tags, author,
                   source_tool, import_method, status, word_count, chunk_count,
                   file_size_bytes, source_url, original_filename, storage_path,
                   current_version, deleted_at, error_message,
                   created_at, updated_at
            FROM client_documents
            {$whereSql}
            ORDER BY {$sortCol} {$dir}, id DESC
            LIMIT {$limit} OFFSET {$offset}";
    $stmt = $db->prepare($sql);
    $stmt->execute($params);
    $rows = $stmt->fetchAll(PDO::FETCH_ASSOC);

    // ACL filter: drop docs whose folder the user can't read.
    $visible = [];
    $aclCache = [];
    foreach ($rows as $row) {
        $fid = isset($row['folder_id']) ? (int)$row['folder_id'] : 0;
        if (!isset($aclCache[$fid])) {
            $aclCache[$fid] = $fid === 0
                ? true
                : dbnDmsUserCanAccessFolder($fid, 'read', $clientId, $userId, $tenantRole);
        }
        if (!$aclCache[$fid]) {
            continue;
        }
        $visible[] = shapeDoc($row);
    }

    dbnToolsRespond([
        'ok'        => true,
        'total'     => $total,
        'offset'    => $offset,
        'limit'     => $limit,
        'documents' => $visible,
        'folder'    => $folderMeta ? [
            'id'        => (int)$folderMeta['id'],
            'name'      => (string)$folderMeta['name'],
            'parent_id' => $folderMeta['parent_id'] ? (int)$folderMeta['parent_id'] : null,
            'color'     => $folderMeta['color'] ?? null,
            'breadcrumb'=> dbnDmsBreadcrumb((int)$folderMeta['id'], $clientId),
        ] : null,
    ]);
}

function respondGet(PDO $db, int $clientId, int $userId, string $tenantRole): void
{
    $id = (int)($_GET['id'] ?? 0);
    if ($id <= 0) {
        dbnToolsError('id is required.', 400, 'missing_id');
    }
    $stmt = $db->prepare('SELECT * FROM client_documents WHERE id = ? AND client_id = ? LIMIT 1');
    $stmt->execute([$id, $clientId]);
    $doc = $stmt->fetch(PDO::FETCH_ASSOC);
    if (!$doc) {
        dbnToolsError('Document not found.', 404, 'not_found');
    }
    $fid = $doc['folder_id'] ? (int)$doc['folder_id'] : 0;
    if (!dbnDmsUserCanAccessFolder($fid ?: null, 'read', $clientId, $userId, $tenantRole)) {
        dbnToolsError('Forbidden.', 403, 'forbidden');
    }

    $chunkRows = [];
    try {
        $chunks = $db->prepare(
            'SELECT id, content, section_title
             FROM client_chunks
             WHERE client_id = ? AND document_id = ?
             ORDER BY id ASC LIMIT 200'
        );
        $chunks->execute([$clientId, $id]);
        $chunkRows = $chunks->fetchAll();
    } catch (Throwable $e) {
        // tolerated
    }

    $versions = [];
    try {
        $vstmt = $db->prepare(
            'SELECT id, version_number, title, original_filename, file_size_bytes, word_count,
                    uploaded_by, notes, created_at
             FROM client_document_versions
             WHERE document_id = ? AND client_id = ?
             ORDER BY version_number DESC LIMIT 50'
        );
        $vstmt->execute([$id, $clientId]);
        $versions = $vstmt->fetchAll();
    } catch (Throwable $e) {
        // table may not exist yet
    }

    $permissions = [
        'can_read'   => true,
        'can_write'  => dbnDmsUserCanAccessFolder($fid ?: null, 'write', $clientId, $userId, $tenantRole),
        'can_manage' => dbnDmsUserCanAccessFolder($fid ?: null, 'manage', $clientId, $userId, $tenantRole),
    ];

    dbnDmsLogAudit($clientId, $userId ?: null, 'view', [], $id, $fid ?: null);

    dbnToolsRespond([
        'ok'          => true,
        'document'    => shapeDoc($doc) + [
            'content'    => (string)($doc['content'] ?? ''),
            'breadcrumb' => dbnDmsBreadcrumb($fid ?: null, $clientId),
        ],
        'chunks'      => $chunkRows,
        'versions'    => $versions,
        'permissions' => $permissions,
    ]);
}

function respondUpdate(PDO $db, int $clientId, int $userId, string $tenantRole): void
{
    $input = dbnToolsJsonInput(20_000);
    $id    = (int)($input['id'] ?? 0);
    if ($id <= 0) {
        dbnToolsError('id is required.', 400, 'missing_id');
    }

    // Load doc to ACL-check current folder.
    $cur = $db->prepare('SELECT id, folder_id FROM client_documents WHERE id = ? AND client_id = ?');
    $cur->execute([$id, $clientId]);
    $existing = $cur->fetch();
    if (!$existing) {
        dbnToolsError('Document not found.', 404, 'not_found');
    }
    $existingFid = $existing['folder_id'] ? (int)$existing['folder_id'] : 0;
    if (!dbnDmsUserCanAccessFolder($existingFid ?: null, 'write', $clientId, $userId, $tenantRole)) {
        dbnToolsError('Forbidden on source folder.', 403, 'forbidden_source');
    }

    $allowed = [
        'title'    => 500,
        'category' => 50,
        'tags'     => 500,
        'language' => 10,
        'author'   => 200,
    ];

    $fields = [];
    $params = [];
    foreach ($allowed as $col => $max) {
        if (!array_key_exists($col, $input)) {
            continue;
        }
        $val = trim((string)$input[$col]);
        if (mb_strlen($val, 'UTF-8') > $max) {
            dbnToolsError("Field {$col} exceeds {$max} chars.", 422, 'field_too_long');
        }
        $fields[] = "{$col} = ?";
        $params[] = $val !== '' ? $val : null;
    }

    // folder_id move
    $movedTo = null;
    if (array_key_exists('folder_id', $input)) {
        $newFid = $input['folder_id'] === null || $input['folder_id'] === '' ? null : (int)$input['folder_id'];
        if ($newFid !== null && $newFid > 0) {
            if (!dbnDmsUserCanAccessFolder($newFid, 'write', $clientId, $userId, $tenantRole)) {
                dbnToolsError('Forbidden on destination folder.', 403, 'forbidden_dest');
            }
            $check = $db->prepare('SELECT id FROM client_folders WHERE id = ? AND client_id = ? AND deleted_at IS NULL');
            $check->execute([$newFid, $clientId]);
            if (!$check->fetchColumn()) {
                dbnToolsError('Destination folder not found.', 404, 'folder_not_found');
            }
        }
        $fields[] = 'folder_id = ?';
        $params[] = $newFid;
        $movedTo = $newFid;
    }

    if (!$fields) {
        dbnToolsError('No editable fields supplied.', 400, 'no_fields');
    }
    $params[] = $id;
    $params[] = $clientId;

    $stmt = $db->prepare(
        'UPDATE client_documents SET ' . implode(', ', $fields)
        . ', updated_at = NOW() WHERE id = ? AND client_id = ?'
    );
    $stmt->execute($params);

    dbnDmsLogAudit($clientId, $userId ?: null,
        $movedTo !== null ? 'move' : 'edit',
        ['fields' => array_keys(array_intersect_key($input, $allowed)),
         'new_folder_id' => $movedTo],
        $id, $movedTo);

    $stmt = $db->prepare('SELECT * FROM client_documents WHERE id = ? AND client_id = ? LIMIT 1');
    $stmt->execute([$id, $clientId]);
    $doc = $stmt->fetch(PDO::FETCH_ASSOC);

    dbnToolsRespond(['ok' => true, 'document' => shapeDoc($doc ?: [])]);
}

function respondDelete(PDO $db, int $clientId, int $userId, string $tenantRole): void
{
    $input = dbnToolsJsonInput(50_000);
    $ids   = $input['ids'] ?? [];
    $hardDelete = !empty($input['hard_delete']);
    if (!is_array($ids) || !$ids) {
        dbnToolsError('ids array is required.', 400, 'missing_ids');
    }
    $ids = array_values(array_unique(array_map('intval', $ids)));
    $ids = array_filter($ids, fn($v) => $v > 0);
    if (!$ids) {
        dbnToolsError('No valid ids.', 400, 'invalid_ids');
    }
    if (count($ids) > 500) {
        dbnToolsError('Cannot delete more than 500 documents at once.', 422, 'too_many');
    }

    // ACL-check each doc's folder.
    $placeholders = implode(',', array_fill(0, count($ids), '?'));
    $rows = $db->prepare("SELECT id, folder_id FROM client_documents WHERE client_id = ? AND id IN ({$placeholders})");
    $rows->execute(array_merge([$clientId], $ids));
    $allowedIds = [];
    foreach ($rows->fetchAll() as $r) {
        $fid = $r['folder_id'] ? (int)$r['folder_id'] : 0;
        if (dbnDmsUserCanAccessFolder($fid ?: null, 'write', $clientId, $userId, $tenantRole)) {
            $allowedIds[] = (int)$r['id'];
        }
    }
    if (!$allowedIds) {
        dbnToolsError('Nothing to delete (insufficient permissions).', 403, 'forbidden');
    }
    $ph = implode(',', array_fill(0, count($allowedIds), '?'));

    if ($hardDelete) {
        if (!in_array($tenantRole, ['admin','owner'], true)) {
            dbnToolsError('Hard delete requires admin role.', 403, 'forbidden_hard_delete');
        }
        $stmt = $db->prepare("DELETE FROM client_documents WHERE client_id = ? AND id IN ({$ph})");
        $stmt->execute(array_merge([$clientId], $allowedIds));
        try {
            $chunks = $db->prepare("DELETE FROM client_chunks WHERE client_id = ? AND document_id IN ({$ph})");
            $chunks->execute(array_merge([$clientId], $allowedIds));
        } catch (Throwable $e) { /* tolerated */ }
        dbnDmsLogAudit($clientId, $userId ?: null, 'delete_hard', ['count' => count($allowedIds), 'ids' => $allowedIds]);
        dbnToolsRespond(['ok' => true, 'deleted' => $stmt->rowCount(), 'hard' => true]);
    }

    // Soft delete (default)
    $stmt = $db->prepare(
        "UPDATE client_documents
         SET deleted_at = NOW(), deleted_by = ?, updated_at = NOW()
         WHERE client_id = ? AND id IN ({$ph}) AND deleted_at IS NULL"
    );
    $stmt->execute(array_merge([$userId ?: null, $clientId], $allowedIds));
    dbnDmsLogAudit($clientId, $userId ?: null, 'delete', ['count' => count($allowedIds), 'ids' => $allowedIds]);
    dbnToolsRespond(['ok' => true, 'deleted' => $stmt->rowCount(), 'hard' => false]);
}

function respondRestore(PDO $db, int $clientId, int $userId, string $tenantRole): void
{
    $input = dbnToolsJsonInput(50_000);
    $ids   = $input['ids'] ?? [];
    if (!is_array($ids) || !$ids) {
        dbnToolsError('ids array is required.', 400, 'missing_ids');
    }
    $ids = array_values(array_unique(array_map('intval', $ids)));
    $ids = array_filter($ids, fn($v) => $v > 0);
    if (!$ids) {
        dbnToolsError('No valid ids.', 400, 'invalid_ids');
    }

    $placeholders = implode(',', array_fill(0, count($ids), '?'));
    $rows = $db->prepare("SELECT id, folder_id FROM client_documents WHERE client_id = ? AND id IN ({$placeholders}) AND deleted_at IS NOT NULL");
    $rows->execute(array_merge([$clientId], $ids));
    $allowedIds = [];
    foreach ($rows->fetchAll() as $r) {
        $fid = $r['folder_id'] ? (int)$r['folder_id'] : 0;
        if (dbnDmsUserCanAccessFolder($fid ?: null, 'write', $clientId, $userId, $tenantRole)) {
            $allowedIds[] = (int)$r['id'];
        }
    }
    if (!$allowedIds) {
        dbnToolsRespond(['ok' => true, 'restored' => 0]);
    }
    $ph = implode(',', array_fill(0, count($allowedIds), '?'));
    $stmt = $db->prepare(
        "UPDATE client_documents
         SET deleted_at = NULL, deleted_by = NULL, updated_at = NOW()
         WHERE client_id = ? AND id IN ({$ph})"
    );
    $stmt->execute(array_merge([$clientId], $allowedIds));
    dbnDmsLogAudit($clientId, $userId ?: null, 'restore', ['count' => count($allowedIds), 'ids' => $allowedIds]);
    dbnToolsRespond(['ok' => true, 'restored' => $stmt->rowCount()]);
}

function shapeDoc(array $row): array
{
    return [
        'id'              => (int)($row['id'] ?? 0),
        'folder_id'       => isset($row['folder_id']) && $row['folder_id'] !== null ? (int)$row['folder_id'] : null,
        'title'           => (string)($row['title'] ?? ''),
        'source_type'     => (string)($row['source_type'] ?? ''),
        'language'        => (string)($row['language'] ?? ''),
        'category'        => (string)($row['category'] ?? ''),
        'tags'            => (string)($row['tags'] ?? ''),
        'author'          => $row['author'] ?? null,
        'source_url'      => $row['source_url'] ?? null,
        'source_tool'     => $row['source_tool'] ?? null,
        'import_method'   => (string)($row['import_method'] ?? ''),
        'status'          => (string)($row['status'] ?? ''),
        'word_count'      => (int)($row['word_count'] ?? 0),
        'chunk_count'     => (int)($row['chunk_count'] ?? 0),
        'file_size_bytes' => (int)($row['file_size_bytes'] ?? 0),
        'original_filename'=> $row['original_filename'] ?? null,
        'has_storage'     => !empty($row['storage_path']),
        'current_version' => (int)($row['current_version'] ?? 1),
        'deleted_at'      => $row['deleted_at'] ?? null,
        'error_message'   => $row['error_message'] ?? null,
        'created_at'      => (string)($row['created_at'] ?? ''),
        'updated_at'      => (string)($row['updated_at'] ?? ''),
    ];
}

function dbnDmsCollectSubtreeIdsForList(PDO $db, int $rootId, int $clientId): array
{
    $collected = [];
    $stack = [$rootId];
    $guard = 0;
    while ($stack && $guard++ < 1000) {
        $batch = $stack;
        $stack = [];
        $ph = implode(',', array_fill(0, count($batch), '?'));
        $stmt = $db->prepare(
            "SELECT id FROM client_folders
             WHERE client_id = ? AND parent_id IN ({$ph}) 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;
}
