import UIKit

func errorAlert(_ message: String) -> UIAlertController {
    let alert = UIAlertController(title: "Something went wrong", message: message, preferredStyle: .alert)
    alert.addAction(UIAlertAction(title: "OK", style: .default))
    return alert
}

extension UIViewController {
    func show(error: Error) {
        present(errorAlert(error.localizedDescription), animated: true)
    }

    func beginNavigationLoading(_ spinner: UIActivityIndicatorView) {
        guard navigationItem.rightBarButtonItem == nil else { return }
        spinner.startAnimating()
        navigationItem.rightBarButtonItem = UIBarButtonItem(customView: spinner)
    }

    func endNavigationLoading(_ spinner: UIActivityIndicatorView) {
        spinner.stopAnimating()
        if navigationItem.rightBarButtonItem?.customView === spinner {
            navigationItem.rightBarButtonItem = nil
        }
    }
}

@MainActor
class RefreshingTableViewController: UITableViewController {
    private let spinner = UIActivityIndicatorView(style: .medium)
    var loadingTask: Task<Void, Never>?

    init() {
        super.init(style: .plain)
        tableView.backgroundColor = .systemGroupedBackground
        tableView.separatorInset = .zero
        tableView.refreshControl = UIRefreshControl()
        tableView.refreshControl?.addTarget(self, action: #selector(refreshRequested), for: .valueChanged)
        navigationItem.largeTitleDisplayMode = .never
    }

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

    deinit { loadingTask?.cancel() }

    func loadContent(refreshing: Bool) {}

    func beginLoading(refreshing: Bool) {
        if !refreshing { beginNavigationLoading(spinner) }
    }

    func endLoading() {
        endNavigationLoading(spinner)
        refreshControl?.endRefreshing()
    }

    @objc private func refreshRequested() {
        loadContent(refreshing: true)
    }
}

final class EmptyBackgroundView: UIView {
    init(title: String, detail: String) {
        super.init(frame: .zero)
        let titleLabel = UILabel()
        titleLabel.text = title
        titleLabel.font = .preferredFont(forTextStyle: .title2)
        titleLabel.textAlignment = .center
        let detailLabel = UILabel()
        detailLabel.text = detail
        detailLabel.font = .preferredFont(forTextStyle: .body)
        detailLabel.textColor = .secondaryLabel
        detailLabel.textAlignment = .center
        detailLabel.numberOfLines = 0
        let stack = UIStackView(arrangedSubviews: [titleLabel, detailLabel])
        stack.axis = .vertical
        stack.spacing = 8
        stack.translatesAutoresizingMaskIntoConstraints = false
        addSubview(stack)
        NSLayoutConstraint.activate([
            stack.leadingAnchor.constraint(equalTo: leadingAnchor, constant: 28),
            stack.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -28),
            stack.centerYAnchor.constraint(equalTo: centerYAnchor, constant: -40),
        ])
    }

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

extension UIColor {
    convenience init?(hex: String) {
        let value = hex.trimmingCharacters(in: CharacterSet(charactersIn: "#"))
        guard value.count == 6, let rgb = Int(value, radix: 16) else { return nil }
        self.init(
            red: CGFloat((rgb >> 16) & 0xff) / 255,
            green: CGFloat((rgb >> 8) & 0xff) / 255,
            blue: CGFloat(rgb & 0xff) / 255,
            alpha: 1
        )
    }
}

func markdown(_ source: String, textStyle: UIFont.TextStyle = .body) -> NSAttributedString {
    let attributed = (try? AttributedString(
        markdown: source,
        options: .init(interpretedSyntax: .full)
    )) ?? AttributedString(source)
    var mutable = attributed
    mutable.font = .preferredFont(forTextStyle: textStyle)
    mutable.foregroundColor = UIColor.label
    return NSAttributedString(mutable)
}

func configureTextCell(_ cell: UITableViewCell, title: String, detail: String, image: UIImage? = nil) {
    var content = cell.defaultContentConfiguration()
    content.text = title
    content.secondaryText = detail
    content.secondaryTextProperties.numberOfLines = 2
    content.image = image
    content.imageProperties.tintColor = .tintColor
    cell.contentConfiguration = content
    cell.backgroundColor = .systemBackground
}

func configureRepositoryContentCell(_ cell: UITableViewCell, row: RepositoryContentRow) {
    let directory = row.kind == "dir"
    let detail = directory
        ? "Folder"
        : ByteCountFormatter.string(fromByteCount: row.size, countStyle: .file)
    configureTextCell(
        cell,
        title: row.name,
        detail: detail,
        image: UIImage(systemName: directory ? "folder.fill" : "doc")
    )
    cell.accessoryType = .disclosureIndicator
}

@MainActor
func showRepositoryContent(
    _ row: RepositoryContentRow,
    context: AppContext,
    owner: String,
    repository: String,
    navigationController: UINavigationController?
) {
    let destination: UIViewController = row.kind == "dir"
        ? RepositoryDirectoryViewController(
            context: context,
            owner: owner,
            repository: repository,
            path: row.path
        )
        : RepositoryFileViewController(
            context: context,
            owner: owner,
            repository: repository,
            path: row.path
        )
    navigationController?.pushViewController(destination, animated: true)
}

func sourceLanguage(_ path: String) -> String? {
    let filename = (path as NSString).lastPathComponent.lowercased()
    let filenames = [
        "cmakelists.txt": "cmake", "dockerfile": "dockerfile", "gemfile": "ruby",
        "makefile": "makefile", "podfile": "ruby",
    ]
    if let language = filenames[filename] { return language }
    let languages = [
        "asm": "x86asm", "c": "c", "cc": "cpp", "clj": "clojure", "cpp": "cpp",
        "cs": "csharp", "css": "css", "dart": "dart", "ex": "elixir", "exs": "elixir",
        "fs": "fsharp", "go": "go", "groovy": "groovy", "h": "cpp", "hpp": "cpp",
        "hs": "haskell", "html": "xml", "java": "java", "jl": "julia", "js": "javascript",
        "json": "json", "jsx": "javascript", "kt": "kotlin", "kts": "kotlin", "lua": "lua",
        "m": "objectivec", "md": "markdown", "mm": "objectivec", "php": "php", "pl": "perl",
        "plist": "xml", "ps1": "powershell", "py": "python", "r": "r", "rb": "ruby",
        "rs": "rust", "scala": "scala", "scss": "scss", "sh": "bash", "sql": "sql",
        "swift": "swift", "toml": "ini", "ts": "typescript", "tsx": "typescript",
        "txt": "plaintext", "xml": "xml", "yaml": "yaml", "yml": "yaml",
    ]
    return languages[(path as NSString).pathExtension.lowercased()]
}

func symbolText(_ symbol: String, text: String, font: UIFont, color: UIColor) -> NSAttributedString {
    let output = NSMutableAttributedString()
    if let image = UIImage(systemName: symbol)?.withTintColor(color, renderingMode: .alwaysOriginal) {
        let attachment = NSTextAttachment(image: image)
        attachment.bounds = CGRect(x: 0, y: -2, width: font.pointSize, height: font.pointSize)
        output.append(NSAttributedString(attachment: attachment))
        output.append(NSAttributedString(string: "  "))
    }
    output.append(NSAttributedString(string: text, attributes: [
        .font: font,
        .foregroundColor: color,
    ]))
    return output
}
