-- name: CreateFolder :one
INSERT INTO folders (path, parentId, clientId, createdBy)
VALUES ($1, $2, $3, $4)
RETURNING *;

-- name: GetFolderByID :one
SELECT * FROM folders
WHERE id = $1;

-- name: GetFolderByPath :one
SELECT * FROM folders
WHERE clientId = $1 AND path = $2;

-- name: GetFoldersByClientID :many
SELECT * FROM folders
WHERE clientId = $1
ORDER BY path;

-- name: GetFoldersByParentID :many
SELECT * FROM folders
WHERE parentId = $1
ORDER BY path;

-- name: RenameFolder :exec
UPDATE folders
SET path = $2
WHERE id = $1;

-- name: GetFolderTree :many
WITH RECURSIVE folder_tree AS (
    -- Base case: start with the target folder
    SELECT id, path, parentId, clientId, createdAt, createdBy
    FROM folders
    WHERE id = $1

    UNION ALL

    -- Recursive case: find all child folders
    SELECT f.id, f.path, f.parentId, f.clientId, f.createdAt, f.createdBy
    FROM folders f
    INNER JOIN folder_tree ft ON f.parentId = ft.id
)
SELECT id, path, parentId, clientId, createdAt, createdBy
FROM folder_tree
ORDER BY path;

-- name: GetRootFolders :many
-- Returns folders with null parentId (should only be the "/" root folder for each client)
SELECT * FROM folders
WHERE clientId = $1 AND parentId IS NULL
ORDER BY path;

-- name: GetTopLevelFolders :many
-- Returns folders that are direct children of the root folder "/"
-- These are the user-visible top-level folders (e.g., /folder1, /folder2)
SELECT f.* FROM folders f
INNER JOIN folders root ON f.parentId = root.id
WHERE f.clientId = $1 AND root.path = '/'
ORDER BY f.path;

-- name: GetClientRootFolder :one
-- Returns the root folder "/" for a client
SELECT * FROM folders
WHERE clientId = $1 AND path = '/';

-- name: GetDocumentsByFolder :many
SELECT * FROM documents
WHERE folderId = $1
ORDER BY id;

-- name: CountDocumentsByFolder :one
SELECT COUNT(*) as total FROM documents
WHERE folderId = $1;

-- name: GetFolderLabelCounts :many
SELECT dl.label, COUNT(DISTINCT dl.documentId) as count
FROM documents d
INNER JOIN documentLabels dl ON d.id = dl.documentId
WHERE d.folderId = $1
GROUP BY dl.label
ORDER BY dl.label;

-- name: UpsertFolder :one
-- Insert a folder if it doesn't exist, or return the existing one
-- Used for auto-creating folder hierarchies during document upload
INSERT INTO folders (path, parentId, clientId, createdBy)
VALUES ($1, $2, $3, $4)
ON CONFLICT (clientId, path) DO UPDATE SET path = EXCLUDED.path
RETURNING *;

-- name: CreateFolderIfAbsent :one
-- Insert a folder only when it does not already exist.
-- Batch uploads use this to distinguish newly-created folder paths from
-- pre-existing paths without mutating existing folders.
INSERT INTO folders (path, parentId, clientId, createdBy)
VALUES (@path, @parent_id, @client_id, @created_by)
ON CONFLICT (clientId, path) DO NOTHING
RETURNING *;

-- name: GetDocumentsByFolderEnriched :many
-- Gets documents in a folder with all enriched metadata fields
SELECT
    d.id,
    d.hash,
    d.folderId,
    d.filename,
    d.originalPath,
    d.file_size_bytes
FROM documents d
WHERE d.folderId = $1
ORDER BY d.id;

-- name: GetFolderTreeIDs :many
-- Get all folder IDs in a folder tree (recursive) for cascade delete operations
WITH RECURSIVE folder_tree AS (
    SELECT id, path FROM folders WHERE id = @folder_id
    UNION ALL
    SELECT f.id, f.path FROM folders f
    JOIN folder_tree ft ON f.parentId = ft.id
)
SELECT id, path FROM folder_tree;

-- name: CountDocumentsInFolderTree :one
-- Count documents in a folder tree to check before deletion without include_documents
WITH RECURSIVE folder_tree AS (
    SELECT id FROM folders WHERE id = @folder_id
    UNION ALL
    SELECT f.id FROM folders f
    JOIN folder_tree ft ON f.parentId = ft.id
)
SELECT COUNT(*) FROM documents WHERE folderId IN (SELECT id FROM folder_tree);

-- name: GetDocumentIDsInFolderTree :many
-- Get all document IDs in a folder tree for cascade deletion
WITH RECURSIVE folder_tree AS (
    SELECT id FROM folders WHERE id = @folder_id
    UNION ALL
    SELECT f.id FROM folders f
    JOIN folder_tree ft ON f.parentId = ft.id
)
SELECT id FROM documents WHERE folderId IN (SELECT id FROM folder_tree);

-- name: GetDocumentIDsInFolderTreeForClient :many
-- Chatbot-scoped recursive folder tree walk. Plan §5 mandates that the
-- clientId filter applies at every recursive step AND at the final
-- document selection so a cross-client root cannot leak documents.
--
-- Step-by-step:
--   - Base case: select the root folder only when its clientId matches.
--   - Recursive case: descend through folders.parentId, requiring the
--     child folder's clientId to match. A folder owned by a different
--     client cannot enter the CTE even if its parent did (which it
--     cannot, because the base case already rejects cross-client roots).
--   - Final selection: documents must reside in folder_tree AND share
--     the same clientId, defending against any future case where a
--     document row references a folder owned by a different client.
WITH RECURSIVE folder_tree AS (
    SELECT id FROM folders
    WHERE id = @folder_id::uuid
      AND clientId = @client_id::varchar
    UNION ALL
    SELECT f.id FROM folders f
    JOIN folder_tree ft ON f.parentId = ft.id
    WHERE f.clientId = @client_id::varchar
)
SELECT id FROM documents
WHERE folderId IN (SELECT id FROM folder_tree)
  AND clientId = @client_id::varchar;

-- name: DeleteDocumentUploadsInFolderTree :exec
-- @sqlc-vet-disable
-- Delete documentUploads rows that reference folders in the tree
WITH RECURSIVE folder_tree AS (
    SELECT id FROM folders WHERE id = @folder_id
    UNION ALL
    SELECT f.id FROM folders f
    JOIN folder_tree ft ON f.parentId = ft.id
)
DELETE FROM documentUploads WHERE folder_id IN (SELECT id FROM folder_tree);

-- name: HardDeleteFolderTree :execrows
-- @sqlc-vet-disable
-- Delete all folders in the tree. PostgreSQL handles ordering within the CTE.
-- Returns the number of rows deleted.
WITH RECURSIVE folder_tree AS (
    SELECT id FROM folders WHERE id = @folder_id
    UNION ALL
    SELECT f.id FROM folders f
    JOIN folder_tree ft ON f.parentId = ft.id
)
DELETE FROM folders WHERE id IN (SELECT id FROM folder_tree);

-- name: ListSafeAutoCreatedFolderCandidates :many
-- @sqlc-vet-disable
-- Candidate folders can be deleted only after every batch that touched that
-- folder has finished folder cleanup. The path equality check protects renamed
-- folders from deletion.
SELECT DISTINCT f.id, f.path
FROM folders f
JOIN batch_folder_candidates bfc ON bfc.folder_id = f.id
WHERE bfc.existed_before = false
  AND bfc.path = f.path
  AND f.path <> '/'
  AND NOT EXISTS (
      SELECT 1
      FROM batch_folder_candidates other_bfc
      JOIN batch_uploads bu ON bu.id = other_bfc.batch_id
      WHERE other_bfc.folder_id = f.id
        AND bu.folder_cleanup_completed_at IS NULL
  )
ORDER BY f.path DESC
LIMIT sqlc.arg(limit_count);

-- name: DeleteSafeAutoCreatedFolder :execrows
-- @sqlc-vet-disable
-- Delete one auto-created folder only when it is currently empty and no longer
-- referenced by documents, uploads, or child folders.
DELETE FROM folders f
WHERE f.id = @folder_id
  AND f.path <> '/'
  AND NOT EXISTS (SELECT 1 FROM documents d WHERE d.folderId = f.id)
  AND NOT EXISTS (SELECT 1 FROM documentUploads du WHERE du.folder_id = f.id)
  AND NOT EXISTS (SELECT 1 FROM folders child WHERE child.parentId = f.id);
