//
//  ConversationNotesService.swift
//  Confab
//
//  Manages per-conversation notes.md files in Application Support/oAI/notes/
//
// SPDX-License-Identifier: PolyForm-Noncommercial-1.0.0
// Copyright (C) 2026 Rune Olsen
//
// This file is part of Confab.
//
// Confab is licensed under the PolyForm Noncommercial License 1.0.0.
// You may use, study, modify, and share it for any noncommercial
// purpose. Commercial use — including selling Confab or any part of
// it, standalone or bundled into another product or service —
// requires a separate commercial license from the copyright holder.
//
// See the LICENSE file or
// <https://polyformproject.org/licenses/noncommercial/1.0.0> for
// the full license text. For commercial licensing, contact Rune
// Olsen via <https://confab.no>.


import Foundation
import AppKit

/// Stores each conversation's notes.md as a single file under
/// `~/Library/Application Support/oAI/notes/`. The filename is a human-readable
/// courtesy for anyone browsing in Finder; the conversation's UUID is embedded in the
/// file content itself (a `**ID**:` header, same convention as `ConversationExport`) so
/// identity never depends on the filename surviving a conversation rename.
///
/// All operations are best-effort — a missing or unreadable file is never an error,
/// since notes files are explicitly meant to tolerate being renamed, edited, or
/// deleted by hand outside the app.
nonisolated final class ConversationNotesService {
    static let shared = ConversationNotesService()

    private let baseDirectory: URL = {
        let appSupport = FileManager.default.urls(for: .applicationSupportDirectory,
                                                   in: .userDomainMask).first!
        return appSupport.appendingPathComponent("oAI/notes", isDirectory: true)
    }()

    private func ensureDirectory() {
        try? FileManager.default.createDirectory(at: baseDirectory, withIntermediateDirectories: true)
    }

    /// A human-readable filename derived from the conversation's name, with a short
    /// ID suffix so two same-named conversations never collide.
    func makeFilename(conversationName: String, conversationId: UUID) -> String {
        let base = conversationName.sanitizedForFilename().nonEmptyOrNil ?? "Untitled"
        let suffix = conversationId.uuidString.prefix(4).lowercased()
        return "\(base)-\(suffix).md"
    }

    /// Returns the notes body (with the embedded ID header stripped), or nil if the
    /// file doesn't exist or can't be read.
    func readBody(filename: String) -> String? {
        let url = baseDirectory.appendingPathComponent(filename)
        guard let content = try? String(contentsOf: url, encoding: .utf8) else { return nil }
        return Self.stripIDHeader(from: content)
    }

    /// Returns the file's exact on-disk content, ID header included — used by GitSyncService to
    /// export the note byte-for-byte without needing to know the header format.
    func readRaw(filename: String) -> String? {
        let url = baseDirectory.appendingPathComponent(filename)
        return try? String(contentsOf: url, encoding: .utf8)
    }

    /// Writes content exactly as given, with no header wrapping — used by GitSyncService to import
    /// a pulled note file byte-for-byte (it already carries its own embedded ID header).
    func writeRaw(content: String, filename: String) {
        ensureDirectory()
        let url = baseDirectory.appendingPathComponent(filename)
        try? content.write(to: url, atomically: true, encoding: .utf8)
    }

    /// Writes the full notes body, prefixed with the conversation's embedded ID header.
    func write(body: String, filename: String, conversationId: UUID) {
        ensureDirectory()
        let content = "**ID**: `\(conversationId.uuidString)`\n\n\(body)"
        let url = baseDirectory.appendingPathComponent(filename)
        try? content.write(to: url, atomically: true, encoding: .utf8)
    }

    func delete(filename: String) {
        let url = baseDirectory.appendingPathComponent(filename)
        try? FileManager.default.removeItem(at: url)
    }

    /// Opens the notes folder in Finder (Settings → Advanced → "Open Notes Folder").
    func openNotesFolder() {
        ensureDirectory()
        NSWorkspace.shared.open(baseDirectory)
    }

    nonisolated static func stripIDHeader(from content: String) -> String {
        guard content.hasPrefix("**ID**: `") else { return content }
        var lines = content.components(separatedBy: "\n").dropFirst()
        if lines.first?.isEmpty == true {
            lines = lines.dropFirst()
        }
        return lines.joined(separator: "\n")
    }
}
