import IndieSync
import SwiftData
import SwiftUI

/// Browse notes by project — the manual-organization counterpart to the
/// automatic context ranking, and a sibling to `TagsView`. Unlike tags a note
/// carries at most one project, so browsing here is filing rather than
/// cross-cutting search.
struct ProjectsView: View {
    @Query(sort: \NoteEntity.createdAt, order: .reverse) private var notes: [NoteEntity]

    @AppStorage(ProjectsNaming.storageKey) private var namingRaw = ProjectsNaming.projects.rawValue
    private var naming: ProjectsNaming { ProjectsNaming(rawValue: namingRaw) ?? .projects }

    @AppStorage(ProjectsPrefs.declaredKey) private var declaredRaw = ""
    @AppStorage(ProjectsPrefs.sortKey) private var sortRaw = ProjectsSort.activity.rawValue
    private var sort: ProjectsSort { ProjectsSort(rawValue: sortRaw) ?? .activity }

    @State private var creating = false
    @State private var newName = ""

    var body: some View {
        NavigationStack {
            List {
                ForEach(projects, id: \.name) { entry in
                    NavigationLink(value: entry.name) {
                        HStack {
                            Label(entry.name, systemImage: "folder")
                            Spacer()
                            Text("\(entry.count)")
                                .foregroundStyle(.secondary)
                                .monospacedDigit()
                        }
                    }
                    .swipeActions(edge: .trailing) {
                        // Only a declared-but-empty project can be deleted here —
                        // one a note carries exists because of that note, not
                        // because of a local declaration.
                        if entry.count == 0 {
                            Button(role: .destructive) {
                                removeDeclared(entry.name)
                            } label: {
                                Label("Delete", systemImage: "trash")
                            }
                        }
                    }
                }
            }
            .navigationTitle(naming.plural)
            .navigationDestination(for: String.self) { project in
                ProjectNotesView(project: project)
            }
            .toolbar {
                ToolbarItem(placement: .primaryAction) {
                    Button {
                        creating = true
                    } label: {
                        Label("New \(naming.singular)", systemImage: "plus")
                    }
                }
                ToolbarItem(placement: .primaryAction) {
                    Menu {
                        Picker("Sort By", selection: $sortRaw) {
                            ForEach(ProjectsSort.allCases) { option in
                                Text(option.label).tag(option.rawValue)
                            }
                        }
                    } label: {
                        Label("Sort", systemImage: "arrow.up.arrow.down")
                    }
                }
            }
            .overlay {
                if projects.isEmpty {
                    ContentUnavailableView(
                        "No \(naming.plural.lowercased()) yet",
                        systemImage: "folder",
                        description: Text("Swipe a note in the list to file it under a \(naming.singular.lowercased()), or tap + to create one.")
                    )
                }
            }
            .alert("New \(naming.singular)", isPresented: $creating) {
                TextField("Name", text: $newName)
                Button("Cancel", role: .cancel) {
                    newName = ""
                }
                Button("Create") {
                    create()
                }
            }
        }
    }

    /// Live projects (from notes that carry them) unioned with names
    /// declared-but-not-yet-used, sorted per `sort`.
    private var projects: [(name: String, count: Int, lastActivity: Date?)] {
        var counts: [String: Int] = [:]
        var lastActivity: [String: Date] = [:]
        for note in notes where note.isLive {
            guard let project = note.project else { continue }
            counts[project, default: 0] += 1
            lastActivity[project] = max(lastActivity[project] ?? note.createdAt, note.createdAt)
        }
        for name in ProjectsPrefs.decodeDeclared(declaredRaw) where counts[name] == nil {
            counts[name] = 0
        }

        let entries = counts.map { (name: $0.key, count: $0.value, lastActivity: lastActivity[$0.key]) }
        switch sort {
        case .activity:
            return entries.sorted { lhs, rhs in
                switch (lhs.lastActivity, rhs.lastActivity) {
                case let (l?, r?): l > r
                case (nil, nil): lhs.name.localizedCaseInsensitiveCompare(rhs.name) == .orderedAscending
                case (nil, _): false
                case (_, nil): true
                }
            }
        case .name:
            return entries.sorted { $0.name.localizedCaseInsensitiveCompare($1.name) == .orderedAscending }
        case .count:
            return entries.sorted { $0.count == $1.count ? $0.name < $1.name : $0.count > $1.count }
        }
    }

    private func create() {
        let trimmed = newName.trimmingCharacters(in: .whitespacesAndNewlines)
        newName = ""
        guard !trimmed.isEmpty, !projects.contains(where: { $0.name == trimmed }) else { return }
        declaredRaw = ProjectsPrefs.encodeDeclared(ProjectsPrefs.decodeDeclared(declaredRaw) + [trimmed])
    }

    private func removeDeclared(_ name: String) {
        declaredRaw = ProjectsPrefs.encodeDeclared(ProjectsPrefs.decodeDeclared(declaredRaw).filter { $0 != name })
    }
}

struct ProjectNotesView: View {
    let project: String

    @Environment(SyncEngine.self) private var syncEngine
    @Query(sort: \NoteEntity.createdAt, order: .reverse) private var notes: [NoteEntity]

    @State private var composing = false
    @State private var editingNote: NoteEntity?
    @State private var assigningNote: NoteEntity?

    @AppStorage(ProjectsNaming.storageKey) private var namingRaw = ProjectsNaming.projects.rawValue
    private var naming: ProjectsNaming { ProjectsNaming(rawValue: namingRaw) ?? .projects }

    var body: some View {
        List {
            ForEach(notes.filter { $0.isLive && $0.project == project }) { note in
                row(note)
            }
        }
        .navigationTitle(project)
        .toolbar {
            ToolbarItem(placement: .primaryAction) {
                Button {
                    composing = true
                } label: {
                    Label("New Note", systemImage: "square.and.pencil")
                }
            }
        }
        .sheet(isPresented: $composing) {
            NoteEditorView(mode: .create, defaultProject: project)
        }
        .sheet(item: $editingNote) { note in
            NoteEditorView(mode: .edit(note))
        }
        .sheet(item: $assigningNote) { note in
            AssignProjectSheet(note: note)
        }
    }

    private func row(_ note: NoteEntity) -> some View {
        Button {
            editingNote = note
        } label: {
            NoteRowView(note: note)
                .frame(maxWidth: .infinity, alignment: .leading)
                .contentShape(Rectangle())
        }
        .buttonStyle(.plain)
        .swipeActions(edge: .leading) {
            Button {
                assigningNote = note
            } label: {
                Label(naming.singular, systemImage: "folder")
            }
            .tint(.indigo)
        }
        .swipeActions(edge: .trailing) {
            Button(role: .destructive) {
                delete(note)
            } label: {
                Label("Delete", systemImage: "trash")
            }
        }
    }

    private func delete(_ note: NoteEntity) {
        guard let id = ULID(string: note.id) else { return }
        Task {
            try? await syncEngine.deleteNote(id: id)
        }
    }
}
