import Foundation

/// **File ▸ Export ▸ …, minus the menu** — which format writes which text, and the one write that puts
/// it where the save panel said (15-import-export.md ▸ Export is three rows).
///
/// A thin router by design: every rule about *what* a format looks like belongs to that format's own
/// writer, so this file has nothing to get out of step with. Its one piece of real behaviour is the
/// write, and the reason that lives here rather than at the command is that a `BoardWriteError` is what
/// the banner speaks, and composing one is not a view's job.
public enum BoardExporter {

    /// The document, as text.
    public static func text(for board: InterchangeBoard, format: InterchangeFormat) -> String {
        switch format {
        case .obsidianKanban, .markdownOutline:
            MarkdownBoardWriter.text(for: board, flavor: format)
        case .csv:
            CSVBoardWriter.text(for: board)
        }
    }

    /// Writes the document to the URL the save panel returned.
    ///
    /// **Atomic, and that is the whole of the ceremony.** `Data.write(options: .atomic)` stages beside
    /// the destination and renames, so a failure part-way leaves the previous file — or no file —
    /// rather than a truncated one. It deliberately does **not** go through `BoardWriter.atomicReplace`:
    /// that path is instrumented for the board's own tree (`EchoLedger`, the `.gitignore`d temp-name
    /// pattern, the write bracket's expectations), and an export lands somewhere the app has no
    /// relationship with beyond this one grant.
    ///
    /// **UTF-8, no BOM** (01-storage-format.md § Encoding and line endings) — the same encoding the app
    /// writes everything else in, and the one every consumer of these three formats expects.
    ///
    /// - Parameter boardTitle: for the failure sentence only; the bytes do not depend on it.
    public static func write(
        _ text: String,
        to url: URL,
        boardTitle: String?
    ) throws(BoardWriteError) {
        do {
            try Data(text.utf8).write(to: url, options: .atomic)
        } catch {
            throw BoardWriteError(
                operation: .exportBoard(title: boardTitle),
                path: url.path,
                reason: .io(message: error.localizedDescription)
            )
        }
    }

    /// The name the save panel opens with — the board's own, with the format's extension.
    ///
    /// Path separators and colons are replaced rather than stripped: `:` is what the Finder shows as `/`
    /// and `/` is what the filesystem refuses, so a board titled "Q3: ship/slip" suggests
    /// "Q3- ship-slip.md" instead of a name the panel would reject. The user can rename it to anything
    /// they like — this is a suggestion, and the panel's answer is honored verbatim.
    public static func suggestedFileName(boardTitle: String, format: InterchangeFormat) -> String {
        var name = boardTitle
            .replacingOccurrences(of: "/", with: "-")
            .replacingOccurrences(of: ":", with: "-")
            .trimmingCharacters(in: .whitespacesAndNewlines)
        if name.isEmpty { name = "Board" }
        return "\(name).\(format.fileExtension)"
    }
}
