import IndieSync
import SwiftData
import SwiftUI

/// The Search tab: full-library search over note text, tags, and capture
/// location. Lives behind the tab bar's dedicated search tab (`role: .search`),
/// which iOS 26 renders as the separated magnifier at the trailing edge.
struct SearchView: View {
    @Environment(SyncEngine.self) private var syncEngine
    @Query(sort: \NoteEntity.createdAt, order: .reverse) private var notes: [NoteEntity]

    @State private var searchText = ""
    @State private var editingNote: NoteEntity?

    var body: some View {
        NavigationStack {
            List {
                ForEach(results) { note in
                    row(note)
                }
            }
            .navigationTitle("Search")
            .searchable(text: $searchText, prompt: "Search notes and tags")
            .overlay {
                if searchText.isEmpty {
                    ContentUnavailableView(
                        "Search your notes",
                        systemImage: "magnifyingglass",
                        description: Text("Find notes by text, tag, or the place you captured them.")
                    )
                } else if results.isEmpty {
                    ContentUnavailableView.search(text: searchText)
                }
            }
            .sheet(item: $editingNote) { note in
                NoteEditorView(mode: .edit(note))
            }
        }
    }

    private func row(_ note: NoteEntity) -> some View {
        Button {
            editingNote = note
        } label: {
            NoteRowView(note: note)
        }
        .buttonStyle(.plain)
        .swipeActions(edge: .trailing) {
            Button(role: .destructive) {
                delete(note)
            } label: {
                Label("Delete", systemImage: "trash")
            }
        }
    }

    private var results: [NoteEntity] {
        guard !searchText.isEmpty else { return [] }
        return notes
            .compactMap { note in note.searchScore(for: searchText).map { (note, $0) } }
            .sorted { lhs, rhs in
                lhs.1 != rhs.1 ? lhs.1 > rhs.1 : lhs.0.createdAt > rhs.0.createdAt
            }
            .map(\.0)
    }

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

extension NoteEntity {
    /// Case-insensitive relevance of this note to a search, or nil when nothing
    /// matches. The score is the strongest matching field: the user's own text
    /// and tags outrank transcript matches, and a transcript match is weighted
    /// by the engine's confidence in it — a shaky transcription can still be
    /// found, it just ranks below solid matches. Confidence-less engines get a
    /// mid-scale weight rather than sinking to the bottom.
    func searchScore(for search: String) -> Double? {
        let needle = search.lowercased()
        var score = 0.0
        if text.lowercased().contains(needle) { score = max(score, 1.0) }
        if tags.contains(where: { $0.lowercased().contains(needle) }) { score = max(score, 0.9) }
        if let transcript = transcriptText, transcript.lowercased().contains(needle) {
            score = max(score, 0.5 + 0.4 * (transcriptConfidence ?? 0.5))
        }
        if placeName?.lowercased().contains(needle) == true
            || locality?.lowercased().contains(needle) == true {
            score = max(score, 0.4)
        }
        return score > 0 ? score : nil
    }
}
