import ForgejoKit
import SwiftUI

struct IssueEditView: View {
    let repository: Repository
    let issue: Issue
    @State private var authService: AuthenticationService
    @State private var title: String
    @State private var bodyText: String
    @State private var isOpen: Bool
    @State private var isSubmitting = false
    @State private var errorMessage: String?
    @State private var showError = false
    @State private var availableLabels: [IssueLabel] = []
    @State private var availableMilestones: [IssueMilestone] = []
    @State private var availableAssignees: [User] = []
    @State private var selectedLabelIDs: Set<Int>
    @State private var selectedMilestoneID: Int?
    @State private var selectedAssigneeLogins: Set<String>
    @Environment(\.dismiss) private var dismiss

    private let issueService: IssueService?
    private let repositoryService: RepositoryService?
    private let onUploadImage: ((Data, String, String) async throws -> String)?
    private let onSaved: (Issue) -> Void

    init(
        repository: Repository, issue: Issue, authService: AuthenticationService,
        onUploadImage: ((Data, String, String) async throws -> String)? = nil,
        onSaved: @escaping (Issue) -> Void,
    ) {
        self.repository = repository
        self.issue = issue
        self.authService = authService
        issueService = authService.client.map { IssueService(client: $0) }
        repositoryService = authService.client.map { RepositoryService(client: $0) }
        self.onUploadImage = onUploadImage
        self.onSaved = onSaved
        _title = State(initialValue: issue.title)
        _bodyText = State(initialValue: issue.body ?? "")
        _isOpen = State(initialValue: issue.stateValue == .open)
        _selectedLabelIDs = State(initialValue: Set(issue.labels.map(\.id)))
        _selectedMilestoneID = State(initialValue: issue.milestone?.id)
        _selectedAssigneeLogins = State(initialValue: Set((issue.assignees ?? []).map(\.login)))
    }

    private var owner: String {
        repository.owner
    }

    private var repo: String {
        repository.repoName
    }

    var body: some View {
        NavigationStack {
            Form {
                Section {
                    TextField("Title", text: $title)
                        .accessibilityIdentifier("issue-edit-title-field")
                }

                DescriptionEditorSection(text: $bodyText, onUploadImage: onUploadImage)

                LabelPickerSection(
                    availableLabels: availableLabels,
                    selectedLabelIDs: $selectedLabelIDs,
                )
                MilestonePickerSection(
                    availableMilestones: availableMilestones,
                    selectedMilestoneID: $selectedMilestoneID,
                )
                UserPickerSection(
                    title: "Assignees",
                    availableUsers: availableAssignees,
                    selectedLogins: $selectedAssigneeLogins,
                )

                Section {
                    Toggle("Open", isOn: $isOpen)
                }
            }
            .navigationTitle("Edit Issue")
            .navigationBarTitleDisplayMode(.inline)
            .toolbar {
                ToolbarItem(placement: .cancellationAction) {
                    Button("Cancel") { dismiss() }
                }
                ToolbarItem(placement: .confirmationAction) {
                    Button("Save") {
                        Task { await saveIssue() }
                    }
                    .disabled(title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty || isSubmitting)
                    .accessibilityIdentifier("issue-edit-save")
                }
            }
            .errorAlert(message: $errorMessage, isPresented: $showError)
            .task {
                await loadMetadata()
            }
        }
    }

    private func loadMetadata() async {
        guard let repositoryService else { return }
        let metadata = await loadRepositoryMetadata(service: repositoryService, owner: owner, repo: repo)
        availableLabels = metadata.labels
        availableMilestones = metadata.milestones
        availableAssignees = metadata.assignees
    }

    private func saveIssue() async {
        guard let issueService else { return }
        isSubmitting = true
        do {
            try await issueService.replaceLabels(
                owner: owner,
                repo: repo,
                index: issue.number,
                labelIDs: Array(selectedLabelIDs),
            )
            let updatedIssue = try await issueService.editIssue(
                owner: owner,
                repo: repo,
                index: issue.number,
                title: title.trimmingCharacters(in: .whitespacesAndNewlines),
                body: bodyText.trimmingCharacters(in: .whitespacesAndNewlines),
                state: isOpen ? IssueState.open.rawValue : IssueState.closed.rawValue,
                milestone: selectedMilestoneID ?? 0, // 0 clears the milestone per Forgejo API
                assignees: Array(selectedAssigneeLogins),
            )
            NotificationCenter.default.post(name: .issuesDidChange, object: nil)
            onSaved(updatedIssue)
            dismiss()
        } catch {
            // The calls above run in sequence with no transaction, so an earlier
            // one may already be committed on the server. Re-fetch the issue and
            // push the authoritative state to the detail view so it does not show
            // stale data, then surface the error and keep the sheet open to retry.
            if let refreshed = try? await issueService.fetchIssue(owner: owner, repo: repo, index: issue.number) {
                NotificationCenter.default.post(name: .issuesDidChange, object: nil)
                onSaved(refreshed)
            }
            errorMessage = error.localizedDescription
            showError = true
        }
        isSubmitting = false
    }

    #if DEBUG
        init(preview _: Void, repository: Repository, issue: Issue, authService: AuthenticationService,
             availableLabels: [IssueLabel] = [], availableMilestones: [IssueMilestone] = [],
             availableAssignees: [User] = [])
        {
            self.repository = repository
            self.issue = issue
            self.authService = authService
            issueService = nil
            repositoryService = nil
            onUploadImage = nil
            onSaved = { _ in }
            _title = State(initialValue: issue.title)
            _bodyText = State(initialValue: issue.body ?? "")
            _isOpen = State(initialValue: issue.stateValue == .open)
            _selectedLabelIDs = State(initialValue: Set(issue.labels.map(\.id)))
            _selectedMilestoneID = State(initialValue: issue.milestone?.id)
            _selectedAssigneeLogins = State(initialValue: Set((issue.assignees ?? []).map(\.login)))
            _availableLabels = State(initialValue: availableLabels)
            _availableMilestones = State(initialValue: availableMilestones)
            _availableAssignees = State(initialValue: availableAssignees)
        }
    #endif
}

#if DEBUG
    #Preview {
        IssueEditView(
            preview: (),
            repository: .preview,
            issue: .preview,
            authService: .previewDefault,
            availableLabels: IssueLabel.previewList,
            availableMilestones: [.preview],
            availableAssignees: [.preview, .previewBot],
        )
    }
#endif
