import Highlighter
import SwiftUI
import UIKit

@MainActor
final class IssueEditorViewController: UIViewController, UITextFieldDelegate, UITextViewDelegate {
    private let context: AppContext
    private let owner: String
    private let repository: String
    private let number: Int64?
    private let saved: (Int64) -> Void
    private let scrollView = UIScrollView()
    private let stack = UIStackView()
    private let titleField = UITextField()
    private let modeControl = UISegmentedControl(items: ["Write", "Preview"])
    private let bodyView = UITextView()
    private let previewView = UIView()
    private let labelsButton = UIButton(type: .system)
    private let milestoneButton = UIButton(type: .system)
    private let closedSwitch = UISwitch()
    private let dueSwitch = UISwitch()
    private let duePicker = UIDatePicker()
    private let spinner = UIActivityIndicatorView(style: .medium)
    private var page: IssueEditorPage?
    private var selectedLabels = Set<Int64>()
    private var selectedMilestone: Int64?
    private var task: Task<Void, Never>?
    private var previewController: UIViewController?
    private var editorFont: UIFont {
        UIFontMetrics(forTextStyle: .body).scaledFont(
            for: .monospacedSystemFont(ofSize: 15, weight: .regular)
        )
    }

    init(
        context: AppContext,
        owner: String,
        repository: String,
        number: Int64? = nil,
        saved: @escaping (Int64) -> Void
    ) {
        self.context = context
        self.owner = owner
        self.repository = repository
        self.number = number
        self.saved = saved
        super.init(nibName: nil, bundle: nil)
        title = number == nil ? "New Issue" : "Edit Issue"
    }

    @available(*, unavailable)
    required init?(coder: NSCoder) { fatalError("init(coder:) is not supported") }

    deinit { task?.cancel() }

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = .systemGroupedBackground
        navigationItem.leftBarButtonItem = UIBarButtonItem(
            barButtonSystemItem: .cancel,
            target: self,
            action: #selector(cancel)
        )
        navigationItem.rightBarButtonItem = UIBarButtonItem(
            title: "Save",
            style: .done,
            target: self,
            action: #selector(save)
        )
        navigationItem.rightBarButtonItem = UIBarButtonItem(customView: spinner)
        spinner.startAnimating()
        configureForm()
        task = Task { await load() }
    }

    private func configureForm() {
        scrollView.translatesAutoresizingMaskIntoConstraints = false
        scrollView.keyboardDismissMode = .interactive
        view.addSubview(scrollView)
        stack.axis = .vertical
        stack.spacing = 12
        stack.translatesAutoresizingMaskIntoConstraints = false
        stack.isHidden = true
        scrollView.addSubview(stack)

        titleField.placeholder = "Title"
        titleField.delegate = self
        titleField.font = .preferredFont(forTextStyle: .headline)
        titleField.adjustsFontForContentSizeCategory = true
        titleField.borderStyle = .roundedRect
        titleField.autocapitalizationType = .sentences
        titleField.returnKeyType = .next
        titleField.accessibilityLabel = "Issue title"
        titleField.addTarget(self, action: #selector(titleChanged), for: .editingChanged)

        [labelsButton, milestoneButton].forEach { button in
            var configuration = UIButton.Configuration.tinted()
            configuration.imagePlacement = .leading
            configuration.imagePadding = 8
            button.configuration = configuration
            button.showsMenuAsPrimaryAction = true
            button.contentHorizontalAlignment = .leading
        }
        labelsButton.configuration?.image = context.symbol("tag")
        milestoneButton.configuration?.image = context.symbol("flag")

        let closedLabel = UILabel()
        closedLabel.text = "Closed"
        closedLabel.font = .preferredFont(forTextStyle: .body)
        closedLabel.adjustsFontForContentSizeCategory = true
        let closedRow = UIStackView(arrangedSubviews: [closedLabel, closedSwitch])
        closedRow.alignment = .center
        closedSwitch.accessibilityLabel = "Closed issue"

        let dueLabel = UILabel()
        dueLabel.text = "Due Date"
        dueLabel.font = .preferredFont(forTextStyle: .body)
        dueLabel.adjustsFontForContentSizeCategory = true
        let dueRow = UIStackView(arrangedSubviews: [dueLabel, dueSwitch])
        dueRow.alignment = .center
        dueSwitch.accessibilityLabel = "Set due date"
        dueSwitch.addTarget(self, action: #selector(dueChanged), for: .valueChanged)
        duePicker.datePickerMode = .date
        duePicker.preferredDatePickerStyle = .inline
        duePicker.isHidden = true

        modeControl.selectedSegmentIndex = 0
        modeControl.addTarget(self, action: #selector(modeChanged), for: .valueChanged)
        bodyView.delegate = self
        bodyView.font = editorFont
        bodyView.adjustsFontForContentSizeCategory = true
        bodyView.backgroundColor = .secondarySystemGroupedBackground
        bodyView.layer.cornerRadius = 10
        bodyView.textContainerInset = UIEdgeInsets(top: 12, left: 8, bottom: 12, right: 8)
        bodyView.autocapitalizationType = .sentences
        bodyView.accessibilityLabel = "Issue description in Markdown"
        previewView.backgroundColor = .secondarySystemGroupedBackground
        previewView.layer.cornerRadius = 10
        previewView.accessibilityLabel = "Issue description preview"
        previewView.isHidden = true

        stack.addArrangedSubview(titleField)
        stack.addArrangedSubview(labelsButton)
        stack.addArrangedSubview(milestoneButton)
        stack.addArrangedSubview(closedRow)
        stack.addArrangedSubview(dueRow)
        stack.addArrangedSubview(duePicker)
        stack.addArrangedSubview(modeControl)
        stack.addArrangedSubview(bodyView)
        stack.addArrangedSubview(previewView)
        NSLayoutConstraint.activate([
            scrollView.leadingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.leadingAnchor),
            scrollView.trailingAnchor.constraint(equalTo: view.safeAreaLayoutGuide.trailingAnchor),
            scrollView.topAnchor.constraint(equalTo: view.safeAreaLayoutGuide.topAnchor),
            scrollView.bottomAnchor.constraint(equalTo: view.keyboardLayoutGuide.topAnchor),
            stack.leadingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.leadingAnchor, constant: 16),
            stack.trailingAnchor.constraint(equalTo: scrollView.contentLayoutGuide.trailingAnchor, constant: -16),
            stack.topAnchor.constraint(equalTo: scrollView.contentLayoutGuide.topAnchor, constant: 16),
            stack.bottomAnchor.constraint(equalTo: scrollView.contentLayoutGuide.bottomAnchor, constant: -16),
            stack.widthAnchor.constraint(equalTo: scrollView.frameLayoutGuide.widthAnchor, constant: -32),
            bodyView.heightAnchor.constraint(greaterThanOrEqualToConstant: 280),
            previewView.heightAnchor.constraint(greaterThanOrEqualToConstant: 280),
        ])
    }

    private func load() async {
        do {
            let page = try await context.core.issueEditor(
                owner: owner,
                repository: repository,
                number: number
            )
            guard !Task.isCancelled else { return }
            self.page = page
            titleField.text = page.title
            bodyView.text = page.body
            closedSwitch.isOn = page.closed
            selectedLabels = Set(page.labels.filter(\.selected).map(\.id))
            selectedMilestone = page.milestones.first(where: \.selected)?.id
            if let timestamp = page.dueDate {
                dueSwitch.isOn = true
                duePicker.date = localDate(forUTCTimestamp: timestamp)
                duePicker.isHidden = false
            }
            updateLabelsMenu()
            updateMilestoneMenu()
            highlightBody()
            stack.isHidden = false
            navigationItem.rightBarButtonItem?.isEnabled = !page.title.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
        } catch {
            if !Task.isCancelled { show(error: error) }
        }
        restoreSaveButton()
    }

    private func updateLabelsMenu() {
        guard let page else { return }
        labelsButton.configuration?.title = selectedLabels.isEmpty
            ? "Labels"
            : "Labels (\(selectedLabels.count))"
        labelsButton.accessibilityValue = selectedLabels.isEmpty
            ? "None"
            : page.labels.filter { selectedLabels.contains($0.id) }.map(\.name).joined(separator: ", ")
        let actions = page.labels.map { label in
            UIAction(
                title: label.name,
                attributes: .keepsMenuPresented,
                state: selectedLabels.contains(label.id) ? .on : .off
            ) { [weak self] action in
                guard let self else { return }
                if self.selectedLabels.remove(label.id) == nil { self.selectedLabels.insert(label.id) }
                action.state = self.selectedLabels.contains(label.id) ? .on : .off
                self.updateLabelsMenu()
            }
        }
        labelsButton.menu = UIMenu(children: actions.isEmpty
            ? [UIAction(title: "No labels", attributes: .disabled) { _ in }]
            : actions)
    }

    private func updateMilestoneMenu() {
        guard let page else { return }
        milestoneButton.configuration?.title = page.milestones
            .first(where: { $0.id == selectedMilestone })?.title ?? "Milestone"
        milestoneButton.accessibilityValue = page.milestones
            .first(where: { $0.id == selectedMilestone })?.title ?? "None"
        let none = UIAction(title: "No Milestone", state: selectedMilestone == nil ? .on : .off) {
            [weak self] _ in
            self?.selectedMilestone = nil
            self?.updateMilestoneMenu()
        }
        milestoneButton.menu = UIMenu(options: .singleSelection, children: [none] + page.milestones.map { milestone in
            UIAction(
                title: milestone.title,
                state: milestone.id == selectedMilestone ? .on : .off
            ) { [weak self] _ in
                self?.selectedMilestone = milestone.id
                self?.updateMilestoneMenu()
            }
        })
    }

    func textViewDidChange(_ textView: UITextView) {
        highlightBody()
    }

    func textFieldShouldReturn(_ textField: UITextField) -> Bool {
        bodyView.becomeFirstResponder()
        return true
    }

    private func highlightBody() {
        let source = bodyView.text ?? ""
        let selection = bodyView.selectedRange
        let highlighter = Highlighter()
        highlighter?.setTheme(traitCollection.userInterfaceStyle == .dark ? "atom-one-dark" : "atom-one-light")
        highlighter?.theme.setCodeFont(editorFont)
        bodyView.attributedText = highlighter?.highlight(source, as: "markdown")
            ?? NSAttributedString(string: source, attributes: [
                .font: editorFont,
                .foregroundColor: UIColor.label,
            ])
        bodyView.selectedRange = selection
        bodyView.typingAttributes = [
            .font: editorFont,
            .foregroundColor: UIColor.label,
        ]
    }

    @objc private func modeChanged() {
        let preview = modeControl.selectedSegmentIndex == 1
        if preview {
            previewController?.willMove(toParent: nil)
            previewController?.view.removeFromSuperview()
            previewController?.removeFromParent()
            let controller = UIHostingController(
                rootView: RepositoryMarkdownPreview(source: bodyView.text ?? "")
            )
            addChild(controller)
            controller.view.translatesAutoresizingMaskIntoConstraints = false
            controller.view.layer.cornerRadius = 10
            controller.view.clipsToBounds = true
            previewView.addSubview(controller.view)
            NSLayoutConstraint.activate([
                controller.view.leadingAnchor.constraint(equalTo: previewView.leadingAnchor),
                controller.view.trailingAnchor.constraint(equalTo: previewView.trailingAnchor),
                controller.view.topAnchor.constraint(equalTo: previewView.topAnchor),
                controller.view.bottomAnchor.constraint(equalTo: previewView.bottomAnchor),
            ])
            controller.didMove(toParent: self)
            previewController = controller
        }
        bodyView.isHidden = preview
        previewView.isHidden = !preview
        view.endEditing(true)
    }

    @objc private func dueChanged() {
        duePicker.isHidden = !dueSwitch.isOn
        if dueSwitch.isOn, page?.dueDate == nil { duePicker.date = Date() }
    }

    @objc private func titleChanged() {
        navigationItem.rightBarButtonItem?.isEnabled = !(titleField.text ?? "")
            .trimmingCharacters(in: .whitespacesAndNewlines).isEmpty
    }

    @objc private func save() {
        view.endEditing(true)
        navigationItem.leftBarButtonItem?.isEnabled = false
        navigationItem.rightBarButtonItem = UIBarButtonItem(customView: spinner)
        spinner.startAnimating()
        task?.cancel()
        task = Task {
            do {
                let number = try await context.core.saveIssue(
                    owner: owner,
                    repository: repository,
                    number: number,
                    title: titleField.text ?? "",
                    body: bodyView.text ?? "",
                    labelIds: Array(selectedLabels),
                    milestoneId: selectedMilestone,
                    dueDate: dueSwitch.isOn ? utcTimestamp(forLocalDate: duePicker.date) : nil,
                    closed: closedSwitch.isOn
                )
                guard !Task.isCancelled else { return }
                saved(number)
            } catch {
                if !Task.isCancelled {
                    show(error: error)
                    restoreSaveButton()
                }
            }
        }
    }

    private func restoreSaveButton() {
        spinner.stopAnimating()
        navigationItem.leftBarButtonItem?.isEnabled = true
        navigationItem.rightBarButtonItem = UIBarButtonItem(
            title: "Save",
            style: .done,
            target: self,
            action: #selector(save)
        )
        titleChanged()
    }

    @objc private func cancel() { dismiss(animated: true) }

}
