import SwiftUI

/// The comment composer and the inline comment edit, one transactional sheet — presented from
/// `CardDetailScreen`'s comments section, `CardEditScreen`'s model in miniature: drafts commit on
/// the trailing button or not at all, Cancel guards a dirty draft with a discard confirmation, a
/// failed write keeps the sheet and its draft and raises an alert, and dismissal happens only on
/// the perform's own `.success`. No scenePhase or onDisappear commit anywhere — backgrounding the
/// app leaves the draft waiting.
///
/// ### Compose posts through the card's single draft
///
/// `Post` is `saveCommentDraft` then `postComment` in **one `session.perform` bracket** — the
/// Mac composer's own two primitives, back to back. That makes the on-disk draft load-bearing
/// rather than optional: the composer seeds from it when one exists (it syncs — a comment started
/// on the Mac is offered here to finish, and its attachments ride into the post untouched),
/// because a post that ignored it would silently clobber that other device's words with these.
/// Cancel touches nothing on disk: the phone's transactional model, so the typed text is the
/// sheet's and the draft folder stays exactly as the last device left it.
///
/// ### Edit seeds from the row that opened it
///
/// The one screen in this stack that carries a value rather than IDs alone — deliberately: a
/// comment lives outside the board snapshot, so there is nothing to re-read from on each body
/// evaluation, and the Mac's inline edit session seeds from open-time bytes the same way. The
/// save targets the comment's *id* on disk regardless, so a body that changed elsewhere mid-edit
/// is overwritten by an explicit Save — the same last-writer-wins any two editors of one file
/// have — and a comment deleted elsewhere refuses the write and alerts rather than recreating it.
struct CommentEditorScreen: View {
    enum Mode {
        /// A new comment through the card's single draft.
        case compose
        /// One posted comment's body.
        case edit(Comment)
    }

    let boardRoot: URL
    let laneID: ItemID
    let cardID: ItemID
    let mode: Mode

    @Environment(BoardIndexStore.self) private var index
    @Environment(\.dismiss) private var dismiss

    @State private var bodyDraft = ""
    @State private var seededBody = ""
    @State private var draftAttachments: [String] = []
    @State private var isConfirmingDiscard = false
    @State private var isWriting = false
    @State private var writeError: BoardSessionError?
    @FocusState private var isBodyFocused: Bool

    private var session: BoardSession { index.session(forBoardAt: boardRoot) }

    private var isEditing: Bool {
        if case .edit = mode { return true }
        return false
    }

    private var isDirty: Bool { bodyDraft != seededBody }

    /// Post validation, the emptied-draft rule read forwards: there is something to post exactly
    /// when there would be something to keep — text, or the draft's synced attachments. Edit has
    /// no such gate; an unchanged Save writes nothing (the writer's identical-bytes rule) and a
    /// deliberately emptied body is the user's to write.
    private var canCommit: Bool {
        switch mode {
        case .compose:
            !bodyDraft.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || !draftAttachments.isEmpty
        case .edit:
            true
        }
    }

    var body: some View {
        NavigationStack {
            TextEditor(text: $bodyDraft)
                .focused($isBodyFocused)
                .padding(.horizontal, 12)
                .padding(.top, 8)
                .navigationTitle(isEditing ? "Edit Comment" : "New Comment")
                .navigationBarTitleDisplayMode(.inline)
                .toolbar {
                    ToolbarItem(placement: .cancellationAction) {
                        Button("Cancel") { requestDismiss() }
                    }
                    ToolbarItem(placement: .confirmationAction) {
                        Button(isEditing ? "Save" : "Post") { commit() }
                            .disabled(!canCommit || isWriting)
                    }
                }
                .task { await seed() }
                .confirmationDialog(
                    "Discard Comment?",
                    isPresented: $isConfirmingDiscard,
                    titleVisibility: .visible
                ) {
                    Button("Discard", role: .destructive) { dismiss() }
                    Button("Cancel", role: .cancel) {}
                }
                .alert(
                    isEditing ? "Couldn't Save" : "Couldn't Post",
                    isPresented: Binding(get: { writeError != nil }, set: { if !$0 { writeError = nil } }),
                    presenting: writeError
                ) { _ in
                    Button("OK") { writeError = nil }
                } message: { error in
                    Text(error.description)
                }
        }
        // A sheet — unlike the edit screen's fullScreenCover — has an interactive swipe-dismiss,
        // which would be a silent Cancel past the dirty guard. Disabling it while dirty routes
        // every exit through the two buttons; a clean sheet still swipes away freely.
        .interactiveDismissDisabled(isDirty)
    }

    /// Seeds once, at presentation. Edit seeds synchronously from the row's value; compose reads
    /// the card's single draft off the main actor and applies it only if the user has not already
    /// started typing into the empty editor — the seed must never eat keystrokes.
    private func seed() async {
        switch mode {
        case let .edit(comment):
            bodyDraft = comment.body
            seededBody = comment.body
        case .compose:
            guard let draft = await session.loadCommentDraft(laneID: laneID, cardID: cardID) else { break }
            draftAttachments = draft.attachments
            if bodyDraft.isEmpty {
                bodyDraft = draft.body
                seededBody = draft.body
            }
        }
        isBodyFocused = true
    }

    private func requestDismiss() {
        if isDirty {
            isConfirmingDiscard = true
        } else {
            dismiss()
        }
    }

    /// One `session.perform` bracket either way; `perform` awaits its own reload, which bumps
    /// `snapshotGeneration`, which is what makes the thread under the dismissing sheet already
    /// show the new comment the instant the animation ends.
    private func commit() {
        let body = bodyDraft
        let laneID = self.laneID
        let cardID = self.cardID
        let cardTitle = session.snapshot?.lanes
            .first { $0.id == laneID }?.cards
            .first { $0.id == cardID }?.title.value

        isWriting = true
        Task {
            let outcome: Result<Void, BoardSessionError>
            switch mode {
            case .compose:
                outcome = await session.perform { (root: URL) throws(BoardWriteError) -> Void in
                    let folder = BoardSession.cardFolder(laneID: laneID, cardID: cardID, inRoot: root)
                    try BoardWriter.saveCommentDraft(inCard: folder, body: body, cardTitle: cardTitle)
                    _ = try BoardWriter.postComment(inCard: folder, cardTitle: cardTitle)
                }
            case let .edit(comment):
                let commentID = comment.id
                outcome = await session.perform { (root: URL) throws(BoardWriteError) -> Void in
                    let folder = BoardSession.cardFolder(laneID: laneID, cardID: cardID, inRoot: root)
                    try BoardWriter.editComment(
                        at: CommentThread.commentFolder(commentID, inCard: folder),
                        body: body,
                        cardTitle: cardTitle
                    )
                }
            }
            isWriting = false
            switch outcome {
            case .success:
                dismiss()
            case let .failure(error):
                writeError = error
            }
        }
    }
}
