//
//  FolderPureLogicTests.swift
//  oAITests
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen

import Testing
import Foundation
@testable import Confab

@Suite("Folder tree pure logic")
struct FolderOrderedTreeTests {

    private func makeFolder(_ name: String, parentId: UUID? = nil) -> Folder {
        Folder(name: name, parentId: parentId)
    }

    @Test("Single root folder with no children")
    func singleRoot() {
        let work = makeFolder("Work")
        let result = Folder.orderedTree(from: [work])
        #expect(result.map { $0.folder.id } == [work.id])
        #expect(result.map(\.depth) == [0])
    }

    @Test("Multiple top-level roots preserve their relative order")
    func multipleRootsPreserveOrder() {
        let work = makeFolder("Work")
        let personal = makeFolder("Personal")
        let result = Folder.orderedTree(from: [work, personal])
        #expect(result.map { $0.folder.id } == [work.id, personal.id])
        #expect(result.map(\.depth) == [0, 0])
    }

    @Test("Parent and children interleave depth-first, not grouped by level")
    func parentChildInterleaveDepthFirst() {
        let work = makeFolder("Work")
        let projectA = makeFolder("Project A", parentId: work.id)
        let projectB = makeFolder("Project B", parentId: work.id)
        let personal = makeFolder("Personal")
        let result = Folder.orderedTree(from: [work, projectA, projectB, personal])
        #expect(result.map { $0.folder.id } == [work.id, projectA.id, projectB.id, personal.id])
        #expect(result.map(\.depth) == [0, 1, 1, 0])
    }

    @Test("Deep nesting (4-5 levels) reports correct depths")
    func deepNestingDepths() {
        let l0 = makeFolder("L0")
        let l1 = makeFolder("L1", parentId: l0.id)
        let l2 = makeFolder("L2", parentId: l1.id)
        let l3 = makeFolder("L3", parentId: l2.id)
        let l4 = makeFolder("L4", parentId: l3.id)
        let result = Folder.orderedTree(from: [l0, l1, l2, l3, l4])
        #expect(result.map(\.depth) == [0, 1, 2, 3, 4])
    }

    @Test("Empty input produces an empty tree")
    func emptyInput() {
        #expect(Folder.orderedTree(from: []).isEmpty)
    }
}

@Suite("Folder.isDescendant")
struct FolderIsDescendantTests {

    private func makeFolder(_ name: String, parentId: UUID? = nil) -> Folder {
        Folder(name: name, parentId: parentId)
    }

    @Test("A direct child is a descendant of its parent")
    func directChild() {
        let parent = makeFolder("Work")
        let child = makeFolder("Project A", parentId: parent.id)
        #expect(Folder.isDescendant(child.id, of: parent.id, in: [parent, child]))
    }

    @Test("A grandchild is a descendant of its grandparent")
    func grandchild() {
        let grandparent = makeFolder("Work")
        let parent = makeFolder("Project A", parentId: grandparent.id)
        let child = makeFolder("Sub-task", parentId: parent.id)
        #expect(Folder.isDescendant(child.id, of: grandparent.id, in: [grandparent, parent, child]))
    }

    @Test("An unrelated folder is not a descendant")
    func unrelatedFolder() {
        let work = makeFolder("Work")
        let personal = makeFolder("Personal")
        #expect(Folder.isDescendant(personal.id, of: work.id, in: [work, personal]) == false)
    }

    @Test("A folder is considered a descendant of itself (rejects self-drop)")
    func selfReference() {
        let work = makeFolder("Work")
        #expect(Folder.isDescendant(work.id, of: work.id, in: [work]))
    }

    @Test("A candidate ID absent from the folder list returns false without crashing")
    func candidateAbsent() {
        let work = makeFolder("Work")
        #expect(Folder.isDescendant(UUID(), of: work.id, in: [work]) == false)
    }
}

@Suite("Folder.visibleFolderIds")
struct FolderVisibleIdsTests {

    private func makeFolder(_ name: String, parentId: UUID? = nil) -> Folder {
        Folder(name: name, parentId: parentId)
    }

    @Test("No collapsed folders means everything is visible")
    func noCollapsedAllVisible() {
        let work = makeFolder("Work")
        let projectA = makeFolder("Project A", parentId: work.id)
        let tree = Folder.orderedTree(from: [work, projectA])
        let visible = Folder.visibleFolderIds(tree: tree, collapsed: [])
        #expect(visible == Set([work.id, projectA.id]))
    }

    @Test("Collapsing a mid-depth folder hides all deeper descendants but keeps its own id visible")
    func collapsingMidDepthHidesDescendants() {
        let work = makeFolder("Work")
        let projectA = makeFolder("Project A", parentId: work.id)
        let subTask = makeFolder("Sub-task", parentId: projectA.id)
        let tree = Folder.orderedTree(from: [work, projectA, subTask])
        let visible = Folder.visibleFolderIds(tree: tree, collapsed: [projectA.id])
        #expect(visible == Set([work.id, projectA.id]))
        #expect(!visible.contains(subTask.id))
    }

    @Test("Two unrelated collapsed folders hide their own subtrees independently")
    func independentCollapsedSubtrees() {
        let work = makeFolder("Work")
        let projectA = makeFolder("Project A", parentId: work.id)
        let personal = makeFolder("Personal")
        let hobby = makeFolder("Hobby", parentId: personal.id)
        let tree = Folder.orderedTree(from: [work, projectA, personal, hobby])
        let visible = Folder.visibleFolderIds(tree: tree, collapsed: [work.id, personal.id])
        #expect(visible == Set([work.id, personal.id]))
    }
}

@Suite("DraggedItem")
struct DraggedItemTests {

    @Test("A single conversation round-trips through encode/decode")
    func singleConversationRoundTrip() {
        let id = UUID()
        let item = DraggedItem.conversations([id])
        #expect(DraggedItem(rawValue: item.rawValue) == item)
    }

    @Test("Multiple bundled conversations round-trip through encode/decode")
    func multipleConversationsRoundTrip() {
        let ids = [UUID(), UUID(), UUID()]
        let item = DraggedItem.conversations(ids)
        guard case .conversations(let decoded) = DraggedItem(rawValue: item.rawValue) else {
            Issue.record("Expected .conversations case")
            return
        }
        #expect(decoded == ids)
    }

    @Test("A folder round-trips through encode/decode")
    func folderRoundTrip() {
        let id = UUID()
        let item = DraggedItem.folder(id)
        #expect(DraggedItem(rawValue: item.rawValue) == item)
    }

    @Test("A garbage string decodes to nil")
    func garbageStringIsNil() {
        #expect(DraggedItem(rawValue: "not-a-valid-payload") == nil)
    }
}
