import UIKit

@MainActor
final class HomeViewController: RefreshingTableViewController {
    private enum ActivityFilter: Int {
        case all
        case issues
        case pulls

        var coreValue: HomeActivityFilter {
            switch self {
            case .all: return .all
            case .issues: return .issues
            case .pulls: return .pullRequests
            }
        }
    }

    private let context: AppContext
    private var page: HomePage?
    private var filter = ActivityFilter.all
    private var nextPage: UInt32?
    private var activities: [ActivityRow] { page?.activities ?? [] }

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

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

    override func viewDidLoad() {
        super.viewDidLoad()
        let servers = 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
                )
            }
        )
        servers.accessibilityLabel = "Servers"
        navigationItem.leftBarButtonItem = servers
        let settings = UIBarButtonItem(
            image: context.symbol("gearshape"),
            primaryAction: UIAction { [weak self] _ in
                guard let self else { return }
                self.navigationController?.pushViewController(
                    SettingsViewController(context: self.context),
                    animated: true
                )
            }
        )
        settings.accessibilityLabel = "Settings"
        navigationItem.rightBarButtonItem = settings
    }

    override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)
        guard page == nil else { return }
        loadContent(refreshing: false)
    }

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

    override func loadMoreContent() {
        guard let nextPage else { return }
        loadPage(nextPage, refreshing: false)
    }

    private func loadPage(_ requestedPage: UInt32, refreshing: Bool) {
        if requestedPage == 1 {
            resetPagination()
            beginLoading(refreshing: refreshing)
        }
        loadingTask?.cancel()
        loadingTask = Task {
            do {
                let result = try await context.core.home(
                    page: requestedPage,
                    filter: filter.coreValue
                )
                if requestedPage == 1 {
                    page = result
                } else {
                    page?.activities.append(contentsOf: result.activities)
                    page?.nextPage = result.nextPage
                }
                nextPage = result.nextPage
                finishPagination(hasMore: result.nextPage != nil)
                title = page?.serverName
                if requestedPage == 1 { tableView.tableHeaderView = page.map { page in
                    HeatmapView(
                        page: page,
                        selectedFilter: filter.rawValue,
                        onFilter: { [weak self] index in
                        guard let self, let filter = ActivityFilter(rawValue: index) else { return }
                        guard filter != self.filter else { return }
                        self.filter = filter
                        self.loadPage(1, refreshing: false)
                        },
                        onNotifications: { [weak self] in
                            guard let self else { return }
                            self.navigationController?.pushViewController(
                                NotificationsViewController(context: self.context),
                                animated: true
                            )
                        },
                        onServerActivity: { [weak self] in
                            guard let self else { return }
                            self.navigationController?.pushViewController(
                                ServerActivityViewController(context: self.context),
                                animated: true
                            )
                        }
                    )
                } }
                updateActivities()
            } catch {
                if !Task.isCancelled {
                    show(error: error)
                    failPagination()
                }
            }
            if requestedPage == 1 { endLoading() }
        }
    }

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

    override func tableView(
        _ tableView: UITableView,
        cellForRowAt indexPath: IndexPath
    ) -> UITableViewCell {
        let cell = tableView.dequeueReusableCell(withIdentifier: "activity")
            ?? UITableViewCell(style: .subtitle, reuseIdentifier: "activity")
        let row = activities[indexPath.row]
        configureTextCell(
            cell,
            title: row.title,
            detail: "\(row.detail)\n\(row.meta)",
            image: context.symbol(activitySymbolName(for: row.icon))
        )
        cell.accessoryType = row.target == .none ? .none : .disclosureIndicator
        return cell
    }

    override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        92
    }

    override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        tableView.deselectRow(at: indexPath, animated: true)
        context.route(activities[indexPath.row])
    }

    private func updateActivities() {
        tableView.reloadData()
        tableView.backgroundView = activities.isEmpty
            ? EmptyBackgroundView(
                title: "No matching activity",
                detail: "This server has no recent activity of the selected type."
            )
            : nil
    }

}

func activitySymbolName(for icon: ActivityIcon) -> String {
    switch icon {
    case .pullRequest: return "arrow.triangle.pull"
    case .issue: return "exclamationmark.circle"
    case .branch: return "arrow.triangle.branch"
    case .tag: return "tag"
    case .push: return "arrow.up.circle"
    case .release: return "shippingbox"
    case .repository: return "books.vertical"
    }
}

final class HeatmapView: UIView {
    private let cells: [HeatCell]
    private let calendar = Calendar(identifier: .gregorian)

    init(
        page: HomePage,
        selectedFilter: Int,
        onFilter: @escaping (Int) -> Void,
        onNotifications: @escaping () -> Void,
        onServerActivity: @escaping () -> Void
    ) {
        cells = page.heatCells
        super.init(frame: CGRect(x: 0, y: 0, width: 0, height: 180))
        backgroundColor = .systemBackground
        let title = UILabel(frame: CGRect(x: 16, y: 12, width: 300, height: 22))
        title.text = "Activity · last 9 months"
        title.font = .preferredFont(forTextStyle: .subheadline)
        title.textColor = .secondaryLabel
        addSubview(title)
        let total = UILabel(frame: CGRect(x: 16, y: 108, width: 300, height: 18))
        total.text = "\(page.contributionCount) contributions"
        total.font = .preferredFont(forTextStyle: .caption1)
        total.textColor = .tertiaryLabel
        addSubview(total)
        let filters = UIStackView()
        filters.axis = .horizontal
        filters.spacing = 4
        filters.translatesAutoresizingMaskIntoConstraints = false
        let filterItems = [
            ("clock", "clock.fill", "All activity"),
            ("exclamationmark.circle", "exclamationmark.circle.fill", "Issues"),
            ("arrow.triangle.pull", "arrow.triangle.pull", "Pull requests"),
        ]
        for (index, item) in filterItems.enumerated() {
            let button = UIButton(type: .custom, primaryAction: UIAction { action in
                guard
                    let button = action.sender as? UIButton,
                    let stack = button.superview as? UIStackView
                else { return }
                for case let item as UIButton in stack.arrangedSubviews {
                    item.isSelected = item === button
                    item.tintColor = item.isSelected ? .tintColor : .secondaryLabel
                    item.accessibilityTraits = item.isSelected ? [.button, .selected] : .button
                }
                onFilter(button.tag)
            })
            button.tag = index
            button.setImage(UIImage(systemName: item.0), for: .normal)
            button.setImage(UIImage(systemName: item.1), for: .selected)
            button.isSelected = index == selectedFilter
            button.tintColor = button.isSelected ? .tintColor : .secondaryLabel
            button.accessibilityLabel = item.2
            button.accessibilityTraits = button.isSelected ? [.button, .selected] : .button
            button.widthAnchor.constraint(equalToConstant: 44).isActive = true
            filters.addArrangedSubview(button)
        }
        let serverActivity = UIButton(
            type: .custom,
            primaryAction: UIAction { _ in onServerActivity() }
        )
        serverActivity.setImage(UIImage(systemName: "person.3"), for: .normal)
        serverActivity.tintColor = .secondaryLabel
        serverActivity.accessibilityLabel = "Server Activity"
        serverActivity.widthAnchor.constraint(equalToConstant: 44).isActive = true
        filters.addArrangedSubview(serverActivity)
        let notifications = UIButton(
            type: .custom,
            primaryAction: UIAction { _ in onNotifications() }
        )
        notifications.setImage(UIImage(systemName: "bell"), for: .normal)
        notifications.tintColor = .secondaryLabel
        notifications.accessibilityLabel = "Notifications"
        notifications.widthAnchor.constraint(equalToConstant: 44).isActive = true
        filters.addArrangedSubview(notifications)
        addSubview(filters)
        NSLayoutConstraint.activate([
            filters.centerXAnchor.constraint(equalTo: centerXAnchor),
            filters.topAnchor.constraint(equalTo: topAnchor, constant: 132),
            filters.heightAnchor.constraint(equalToConstant: 44),
        ])
    }

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

    override func draw(_ rect: CGRect) {
        guard
            let first = cells.first,
            let last = cells.last
        else { return }
        let firstDate = Date(timeIntervalSince1970: TimeInterval(first.timestamp))
        let lastDate = Date(timeIntervalSince1970: TimeInterval(last.timestamp))
        guard let firstWeek = calendar.dateInterval(of: .weekOfYear, for: firstDate)?.start else {
            return
        }
        let monthFormatter = DateFormatter()
        monthFormatter.dateFormat = "MMM"
        let months = cells.reduce(into: [Date]()) { result, cell in
            let date = Date(timeIntervalSince1970: TimeInterval(cell.timestamp))
            let month = calendar.date(
                from: calendar.dateComponents([.year, .month], from: date)
            ) ?? date
            if result.last != month { result.append(month) }
        }
        let weekCount = CGFloat(
            (calendar.dateComponents([.day], from: firstWeek, to: lastDate).day ?? 0) / 7 + 1
        )
        let monthGap: CGFloat = 4
        let monthGaps = CGFloat(max(months.count - 1, 0)) * monthGap
        let width = max(4, min(6, (bounds.width - 32 - monthGaps) / weekCount - 1))
        let gap = width + 1
        let graphWidth = (weekCount - 1) * gap + width + monthGaps
        let graphOrigin = max(0, (bounds.width - graphWidth) / 2)
        let labelAttributes: [NSAttributedString.Key: Any] = [
            .font: UIFont.preferredFont(forTextStyle: .caption2),
            .foregroundColor: UIColor.secondaryLabel,
        ]
        for (index, month) in months.enumerated() {
            let days = calendar.dateComponents([.day], from: firstWeek, to: month).day ?? 0
            monthFormatter.string(from: month).draw(
                at: CGPoint(
                    x: graphOrigin + CGFloat(days / 7) * gap + CGFloat(index) * monthGap,
                    y: 34
                ),
                withAttributes: labelAttributes
            )
        }
        for cell in cells {
            let date = Date(timeIntervalSince1970: TimeInterval(cell.timestamp))
            let days = calendar.dateComponents([.day], from: firstWeek, to: date).day ?? 0
            let month = calendar.date(
                from: calendar.dateComponents([.year, .month], from: date)
            ) ?? date
            let monthIndex = months.firstIndex(of: month) ?? 0
            let colors: [UIColor] = [
                .systemGray5,
                UIColor(red: 0.72, green: 0.85, blue: 0.96, alpha: 1),
                UIColor(red: 0.45, green: 0.71, blue: 0.91, alpha: 1),
                UIColor(red: 0.15, green: 0.55, blue: 0.83, alpha: 1),
                UIColor(red: 0.04, green: 0.41, blue: 0.72, alpha: 1),
            ]
            colors[Int(min(cell.level, 4))].setFill()
            UIBezierPath(
                roundedRect: CGRect(
                    x: graphOrigin + CGFloat(days / 7) * gap + CGFloat(monthIndex) * monthGap,
                    y: 48 + CGFloat(calendar.component(.weekday, from: date) - 1) * gap,
                    width: width,
                    height: width
                ),
                cornerRadius: 1
            ).fill()
        }
    }
}
