import UIKit

final class PullCell: UITableViewCell {
    private let stateIcon = UIImageView()
    private let titleLabel = UILabel()
    private let summaryLabel = UILabel()
    private let metaLabel = UILabel()

    override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
        super.init(style: style, reuseIdentifier: reuseIdentifier)
        accessoryType = .disclosureIndicator
        titleLabel.font = .preferredFont(forTextStyle: .headline)
        titleLabel.numberOfLines = 2
        let titleStack = UIStackView(arrangedSubviews: [stateIcon, titleLabel])
        titleStack.alignment = .firstBaseline
        titleStack.spacing = 8
        summaryLabel.font = .preferredFont(forTextStyle: .subheadline)
        summaryLabel.textColor = .secondaryLabel
        summaryLabel.numberOfLines = 2
        metaLabel.font = .preferredFont(forTextStyle: .caption1)
        metaLabel.textColor = .tertiaryLabel
        metaLabel.numberOfLines = 2
        [titleLabel, summaryLabel, metaLabel].forEach {
            $0.adjustsFontForContentSizeCategory = true
        }
        let stack = UIStackView(arrangedSubviews: [titleStack, summaryLabel, metaLabel])
        stack.axis = .vertical
        stack.spacing = 6
        stack.translatesAutoresizingMaskIntoConstraints = false
        contentView.addSubview(stack)
        NSLayoutConstraint.activate([
            stack.leadingAnchor.constraint(equalTo: contentView.leadingAnchor, constant: 16),
            stack.trailingAnchor.constraint(equalTo: contentView.trailingAnchor, constant: -8),
            stack.topAnchor.constraint(equalTo: contentView.topAnchor, constant: 10),
            stack.bottomAnchor.constraint(equalTo: contentView.bottomAnchor, constant: -10),
        ])
    }

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

    func configure(_ row: PullRow) {
        configureOpenClosedStateIcon(
            stateIcon,
            state: row.state,
            subject: "pull request",
            textStyle: .headline
        )
        titleLabel.text = row.title
        summaryLabel.text = row.summary
        metaLabel.text = row.meta
    }
}
@MainActor
final class PullsViewController: RefreshingTableViewController {
    private let context: AppContext
    private var rows: [PullRow] = []
    private var filterOptions: PullFilterOptions?
    private var filterTask: Task<Void, Never>?
    private var currentPage: UInt32 = 0

    init(context: AppContext) {
        self.context = context
        super.init()
        title = "Pull Requests"
    }

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

    deinit { filterTask?.cancel() }

    override func viewDidLoad() {
        super.viewDidLoad()
        tableView.register(PullCell.self, forCellReuseIdentifier: "pull")
        tableView.rowHeight = UITableView.automaticDimension
        tableView.estimatedRowHeight = 116
        navigationItem.leftBarButtonItem = UIBarButtonItem(
            image: context.symbol("server.rack"),
            primaryAction: UIAction { [weak self] _ in
                guard let self else { return }
                self.navigationController?.pushViewController(
                    ServersViewController(context: self.context),
                    animated: true
                )
            }
        )
        updateFilterMenu()
        loadContent(refreshing: false)
    }

    override func loadContent(refreshing: Bool) {
        guard context.core.activeServerIndex() != nil else {
            tableView.backgroundView = EmptyBackgroundView(
                title: "No server selected",
                detail: "Open Issues or Repos to select a server or add your first one."
            )
            refreshControl?.endRefreshing()
            return
        }
        loadFilterOptions()
        loadPulls(refreshing: refreshing)
    }

    private func loadPulls(refreshing: Bool) {
        loadPulls(page: 1, refreshing: refreshing)
    }

    override func loadMoreContent() {
        loadPulls(page: currentPage + 1, refreshing: false)
    }

    private func loadPulls(page: UInt32, refreshing: Bool) {
        if page == 1 {
            resetPagination()
            beginLoading(refreshing: refreshing)
        }
        loadingTask?.cancel()
        loadingTask = Task {
            do {
                let result = try await context.core.pulls(page: page)
                if page == 1 { rows = result.rows } else { rows.append(contentsOf: result.rows) }
                currentPage = page
                finishPagination(hasMore: result.hasMore)
                tableView.reloadData()
                let status = context.core.settings().pullStatus
                tableView.backgroundView = rows.isEmpty
                    ? EmptyBackgroundView(
                        title: "No \(status) pull requests",
                        detail: filtersActive
                            ? "No pull requests match the selected filters."
                            : "No pull requests match the selected status."
                    )
                    : nil
            } catch {
                if !Task.isCancelled {
                    show(error: error)
                    failPagination()
                }
            }
            if page == 1 { endLoading() }
        }
    }

    private func loadFilterOptions() {
        filterTask?.cancel()
        filterTask = Task {
            do {
                filterOptions = try await context.core.pullFilters()
                guard !Task.isCancelled else { return }
                updateFilterMenu()
            } catch {
                if !Task.isCancelled { show(error: error) }
            }
        }
    }

    override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        rows.count
    }

    override func tableView(
        _ tableView: UITableView,
        cellForRowAt indexPath: IndexPath
    ) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "pull", for: indexPath) as! PullCell
        cell.configure(rows[indexPath.row])
        return cell
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        let row = rows[indexPath.row]
        navigationController?.pushViewController(
            PullViewController(
                context: context,
                owner: row.owner,
                repository: row.repository,
                number: row.number
            ),
            animated: true
        )
    }

    private func updateFilterMenu() {
        let current = context.core.settings().pullStatus
        let status = UIMenu(
            title: "Status",
            image: filterMenuImage("circle.lefthalf.filled", active: current != "open"),
            options: .singleSelection,
            children: ["open", "closed"].map { status in
                UIAction(title: status.capitalized, state: current == status ? .on : .off) {
                    [weak self] _ in
                    guard let self else { return }
                    do {
                        try self.context.core.setPullStatus(status: status)
                        self.updateFilterMenu()
                        self.loadPulls(refreshing: false)
                    } catch {
                        self.show(error: error)
                    }
                }
            }
        )
        let milestone: UIMenuElement = filterOptions.map(milestoneMenu)
            ?? UIAction(title: "Loading milestones…", attributes: .disabled) { _ in }
        let item = navigationItem.rightBarButtonItem ?? UIBarButtonItem(
            image: context.symbol("line.3.horizontal.decrease.circle")
        )
        var children: [UIMenuElement] = [status]
        if let filterOptions {
            children.insert(searchAction(filterOptions), at: 0)
        }
        children.append(milestone)
        children.append(clearFiltersMenu())
        item.menu = UIMenu(children: children)
        item.accessibilityLabel = "Filter pull requests"
        navigationItem.rightBarButtonItem = item
        updateFilterTint()
    }

    private func searchAction(_ options: PullFilterOptions) -> UIAction {
        UIAction(
            title: "Search Text",
            subtitle: options.searchText.isEmpty ? "Any text" : options.searchText,
            image: filterMenuImage("magnifyingglass", active: !options.searchText.isEmpty)
        ) { [weak self] _ in
            self?.promptForSearchText(
                title: "Search Pull Requests",
                current: options.searchText
            ) { [weak self] searchText in
                self?.setSearchText(searchText)
            }
        }
    }

    private func milestoneMenu(_ options: PullFilterOptions) -> UIMenu {
        let selected = options.selectedMilestone
        let all = UIAction(title: "All Milestones", state: selected.isEmpty ? .on : .off) {
            [weak self] _ in self?.selectMilestone("")
        }
        let milestones = options.milestones.map { milestone in
            UIAction(title: milestone, state: selected == milestone ? .on : .off) {
                [weak self] _ in self?.selectMilestone(milestone)
            }
        }
        return UIMenu(
            title: "Milestone",
            image: filterMenuImage("flag", active: !selected.isEmpty),
            options: .singleSelection,
            children: [all] + milestones
        )
    }

    private func selectMilestone(_ milestone: String) {
        guard let options = filterOptions else { return }
        filterTask?.cancel()
        do {
            try context.core.setPullFilters(
                milestone: milestone,
                searchText: options.searchText
            )
            filterOptions?.selectedMilestone = milestone
            updateFilterMenu()
            loadPulls(refreshing: false)
        } catch {
            show(error: error)
        }
    }

    private func setSearchText(_ searchText: String) {
        guard let options = filterOptions else { return }
        filterTask?.cancel()
        do {
            try context.core.setPullFilters(
                milestone: options.selectedMilestone,
                searchText: searchText
            )
            filterOptions?.searchText = searchText.trimmingCharacters(in: .whitespacesAndNewlines)
            updateFilterMenu()
            loadPulls(refreshing: false)
        } catch {
            show(error: error)
        }
    }

    private func clearFiltersMenu() -> UIMenu {
        UIMenu(
            options: .displayInline,
            children: [
                UIAction(
                    title: "Clear Filters",
                    image: context.symbol("xmark.circle"),
                    attributes: filtersActive ? [] : .disabled
                ) { [weak self] _ in self?.clearFilters() },
            ]
        )
    }

    private func clearFilters() {
        filterTask?.cancel()
        do {
            try context.core.clearPullFilters()
            filterOptions?.selectedMilestone = ""
            filterOptions?.searchText = ""
            updateFilterMenu()
            loadPulls(refreshing: false)
        } catch {
            show(error: error)
        }
    }

    private var filtersActive: Bool {
        (try? context.core.pullFiltersActive()) ?? false
    }

    private func updateFilterTint() {
        navigationItem.rightBarButtonItem?.tintColor = filtersActive ? .tintColor : .secondaryLabel
        navigationItem.rightBarButtonItem?.accessibilityValue = filtersActive
            ? "Filters active"
            : "Default filters"
    }
}
