//
// Copyright 2023 Signal Messenger, LLC
// SPDX-License-Identifier: AGPL-3.0-only
//

import PureLayout
import SignalCoreKit

public protocol OWSTableViewControllerDelegate: AnyObject {
    func tableViewWillBeginDragging(_ tableView: UITableView)
}

open class OWSTableViewController: OWSViewController {

    public weak var delegate: OWSTableViewControllerDelegate?

    public var contents = OWSTableContents() {
        didSet {
            if oldValue !== contents {
                applyContents()
            }
        }
    }

    public let tableView = UITableView(frame: .zero, style: .grouped)

    public var shouldAvoidKeyboard: Bool = false

    public var layoutMarginsRelativeTableContent: Bool = false

    private enum Constants {
        static let cellReuseIdentifier = "OWSTableCellIdentifier"
    }

    public override func loadView() {
        super.loadView()

        tableView.delegate = self
        tableView.dataSource = self
        tableView.tableFooterView = UIView(frame: .zero)
        view.addSubview(tableView)

        if shouldAvoidKeyboard {
            tableView.autoPinEdgesToSuperviewEdges(with: .zero, excludingEdge: .bottom)
            tableView.autoPinEdge(.bottom, to: .bottom, of: keyboardLayoutGuideView)
        } else {
            tableView.autoPinEdgesToSuperviewEdges()
        }

        tableView.register(UITableViewCell.self, forCellReuseIdentifier: Constants.cellReuseIdentifier)

        configureTableViewLayoutMargins()
        applyContents()
        applyTheme()
    }

    open override func viewWillAppear(_ animated: Bool) {
        super.viewWillAppear(animated)

        tableView.tableFooterView = UIView(frame: .zero)
    }

    /// Reloads table contents when content size category changes.
    ///
    /// Does not reload header/footer views. Subclasses that use header/footer
    /// views that need to update in response to content size category changes
    /// should override this method to do so manually.
    public override func contentSizeCategoryDidChange() {
        super.contentSizeCategoryDidChange()

        // Reload when content size might need to change.
        applyContents()
    }

    // MARK: Appearance

    /// Applies theme and reloads table contents.
    ///
    /// Does not reload header/footer views. Subclasses that use header/footer
    /// views that need to update in response to theme changes should override
    /// this method to do so manually.
    public override func themeDidChange() {
        super.themeDidChange()

        applyTheme()
        tableView.reloadData()
    }

    open func applyTheme() {
        view.backgroundColor = Theme.backgroundColor
        tableView.backgroundColor = Theme.backgroundColor
        tableView.separatorColor = Theme.cellSeparatorColor
    }

    private func configureTableViewLayoutMargins() {
        guard layoutMarginsRelativeTableContent else { return }

        tableView.preservesSuperviewLayoutMargins = true
        tableView.layoutMargins = .zero
    }

    private static var sectionHeaderFooterTextFont: UIFont {
        return UIFont.preferredFont(forTextStyle: .caption1)
    }

    // MARK: Contents

    private func applyContents() {
        if let title = contents.title?.nilIfEmpty {
            self.title = title
        }
        tableView.reloadData()
    }

    private func sectionForIndex(_ index: Int) -> OWSTableSection {
        return contents.sections[index]
    }

    private func itemForIndexPath(_ indexPath: IndexPath) -> OWSTableItem {
        return sectionForIndex(indexPath.section).items[indexPath.item]
    }

    // MARK: Presentation

    public func present(fromViewController viewController: UIViewController) {
        let navigationController = OWSNavigationController(rootViewController: self)
        navigationItem.leftBarButtonItem = .systemItem(.stop) { [weak self] in
            self?.dismiss(animated: true)
        }
        viewController.present(navigationController, animated: true)
    }
}

extension OWSTableViewController: UITableViewDataSource, UITableViewDelegate {

    public func numberOfSections(in tableView: UITableView) -> Int {
        return contents.sections.count
    }

    public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return sectionForIndex(section).itemCount
    }

    public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        let item = itemForIndexPath(indexPath)

        item.tableViewController = self

        if let customCell = item.getOrBuildCustomCell(tableView) {
            return customCell
        }

        let cell = tableView.dequeueReusableCell(withIdentifier: Constants.cellReuseIdentifier, for: indexPath)
        OWSTableItem.configureCell(cell)
        cell.textLabel?.text = item.title
        return cell
    }

    public func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
        if let customHeight = itemForIndexPath(indexPath).customRowHeight {
            return customHeight
        }
        return UITableView.automaticDimension
    }

    public func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
        let section = sectionForIndex(section)

        if let customHeaderView = section.customHeaderView {
            return customHeaderView
        }

        let hasPlainTextTitle = !section.headerTitle.isEmptyOrNil
        let hasAttributedTextTitle = !(section.headerAttributedTitle?.string ?? "").isEmpty
        if hasPlainTextTitle || hasAttributedTextTitle {
            let textView = LinkingTextView()
            textView.textColor = Theme.secondaryTextAndIconColor
            textView.font = OWSTableViewController.sectionHeaderFooterTextFont
            if hasAttributedTextTitle {
                textView.attributedText = section.headerAttributedTitle
            } else {
                textView.text = section.headerTitle?.uppercased()
            }

            let sectionHeaderView = UIView()
            sectionHeaderView.addSubview(textView)
            textView.autoPinHeightToSuperview()

            if layoutMarginsRelativeTableContent {
                sectionHeaderView.preservesSuperviewLayoutMargins = true
                textView.autoPinWidthToSuperviewMargins()
                textView.textContainerInset = UIEdgeInsets(top: 16, leading: 0, bottom: 6, trailing: 0)
            } else {
                textView.autoPinWidthToSuperview()
                let tableEdgeInset: CGFloat = UIDevice.current.isPlusSizePhone ? 20 : 16
                textView.textContainerInset = UIEdgeInsets(top: 16, leading: tableEdgeInset, bottom: 6, trailing: tableEdgeInset)
            }

            return sectionHeaderView
        }

        return nil
    }

    public func tableView(_ tableView: UITableView, viewForFooterInSection section: Int) -> UIView? {
        let tableSection = sectionForIndex(section)

        if let customFooterView = tableSection.customFooterView {
            return customFooterView
        }

        let hasPlainTextTitle = !tableSection.footerTitle.isEmptyOrNil
        let hasAttributedTextTitle = !(tableSection.footerAttributedTitle?.string ?? "").isEmpty
        if hasPlainTextTitle || hasAttributedTextTitle {
            let textView = LinkingTextView()
            textView.textColor = .ows_gray45
            textView.font = OWSTableViewController.sectionHeaderFooterTextFont
            textView.linkTextAttributes = [
                .foregroundColor: Theme.accentBlueColor,
                .underlineStyle: NSUnderlineStyle(),
                .font: OWSTableViewController.sectionHeaderFooterTextFont
            ]
            if hasAttributedTextTitle {
                textView.attributedText = tableSection.footerAttributedTitle
            } else {
                textView.text = tableSection.footerTitle
            }

            let sectionFooterView = UIView()
            sectionFooterView.addSubview(textView)
            textView.autoPinHeightToSuperview()

            if layoutMarginsRelativeTableContent {
                sectionFooterView.preservesSuperviewLayoutMargins = true
                textView.autoPinWidthToSuperviewMargins()
                textView.textContainerInset = UIEdgeInsets(top: 16, leading: 0, bottom: 6, trailing: 0)
            } else {
                textView.autoPinWidthToSuperview()
                let tableEdgeInset: CGFloat = UIDevice.current.isPlusSizePhone ? 20 : 16
                textView.textContainerInset = UIEdgeInsets(top: 16, leading: tableEdgeInset, bottom: 6, trailing: tableEdgeInset)
            }

            return sectionFooterView
        }

        return nil
    }

    public func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
        let tableSection = sectionForIndex(section)

        if let customHeight = tableSection.customHeaderHeight {
            owsAssertDebug(customHeight > 0 || customHeight == UITableView.automaticDimension)
            return customHeight
        }

        if self.tableView(tableView, viewForHeaderInSection: section) != nil {
            return UITableView.automaticDimension
        }

        return 0
   }

    public func tableView(_ tableView: UITableView, heightForFooterInSection section: Int) -> CGFloat {
        let tableSection = sectionForIndex(section)

        if let customHeight = tableSection.customFooterHeight {
            owsAssertDebug(customHeight > 0 || customHeight == UITableView.automaticDimension)
            return customHeight
        }

        if self.tableView(tableView, viewForFooterInSection: section) != nil {
            return UITableView.automaticDimension
        }

        return 0
    }

    public func tableView(_ tableView: UITableView, willDeselectRowAt indexPath: IndexPath) -> IndexPath? {
        let item = itemForIndexPath(indexPath)
        guard item.actionBlock != nil else { return nil }
        return indexPath
    }

    public func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
        if let actionBlock = itemForIndexPath(indexPath).actionBlock {
            actionBlock()
        }
    }

    // MARK: Index

    public func tableView(_ tableView: UITableView, sectionForSectionIndexTitle title: String, at index: Int) -> Int {
        if let sectionForSectionIndexTitleBlock = contents.sectionForSectionIndexTitleBlock {
            return sectionForSectionIndexTitleBlock(title, index)
        }
        return 0
    }

    public func sectionIndexTitles(for tableView: UITableView) -> [String]? {
        if let sectionIndexTitlesForTableViewBlock = contents.sectionIndexTitlesForTableViewBlock {
            return sectionIndexTitlesForTableViewBlock()
        }
        return nil
    }

    // MARK: Editing

    public override func setEditing(_ editing: Bool, animated: Bool) {
        super.setEditing(editing, animated: animated)
        tableView.setEditing(editing, animated: animated)
    }

    public override var isEditing: Bool {
        didSet {
            tableView.isEditing = isEditing
        }
    }
}

extension OWSTableViewController: UIScrollViewDelegate {

    public func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
        delegate?.tableViewWillBeginDragging(tableView)
    }
}
