
struct ButtonsBlock: BlockLineProcessor {

    static let blockId: ContentBlock = .buttons

    let content: Content

    let results: PageGenerationResults

    let language: ContentLanguage

    private let buttons: ButtonBlock

    init(content: Content, results: PageGenerationResults, language: ContentLanguage) {
        self.content = content
        self.results = results
        self.language = language
        self.buttons = .init(content: content, results: results, language: language)
    }

    func process(_ lines: [String], markdown: Substring) -> String {
        let buttons = lines.split(separator: "").compactMap { buttonLines in
            makeButton(buttonLines, markdown: markdown)
        }
        return ContentButtons(items: buttons).content
    }

    func makeButton(_ lines: ArraySlice<String>, markdown: Substring) -> ContentButtons.Item? {
        let arguments: [ButtonBlock.Key : String] = lines.reduce(into: [:]) { dict, line in
            guard line.trimmed != "" else {
                return
            }
            let (rawKey, rawValue) = line.splitAtFirst(":")
            guard let key = ButtonBlock.Key(rawValue: rawKey.trimmed) else {
                print("Invalid key \(rawKey)")
                invalid(markdown)
                return
            }
            dict[key] = rawValue.trimmed
        }
        return self.buttons.process(arguments, markdown: markdown)
    }
}

struct ButtonBlock: KeyedBlockProcessor {
    
    enum Key: String, Equatable {
        case icon
        case file
        case text
        case name
        case url
        case event
    }

    static let blockId: ContentBlock = .button

    let content: Content

    let results: PageGenerationResults

    let language: ContentLanguage

    init(content: Content, results: PageGenerationResults, language: ContentLanguage) {
        self.content = content
        self.results = results
        self.language = language
    }

    func process(_ arguments: [Key : String], markdown: Substring) -> String {
        guard let button = process(arguments, markdown: markdown) else {
            return ""
        }
        return ContentButtons(items: [button]).content
    }

    func process(_ arguments: [Key : String], markdown: Substring) -> ContentButtons.Item? {
        guard let rawIcon = arguments[.icon],
              let text = arguments[.text],
              let icon = PageIcon(rawValue: rawIcon) else {
            invalid(markdown)
            return nil
        }

        if let file = arguments[.file] {
            let name = arguments[.name]
            return download(fileId: file, icon: icon, text: text, filename: name)
        }
        if let url = arguments[.url] {
            return link(url: url, icon: icon, text: text, markdown: markdown)
        }
        if let event = arguments[.event] {
            return action(event: event, icon: icon, text: text)
        }
        invalid(markdown)
        return nil
    }

    private func download(fileId: String, icon: PageIcon, text: String, filename: String?) -> ContentButtons.Item? {
        guard let file = content.file(fileId) else {
            results.missing(file: fileId, source: "Download button")
            return nil
        }
        results.require(file: file)
        results.require(icon: icon)
        return ContentButtons.Item(
            icon: icon,
            filePath: file.absoluteUrl,
            text: text,
            downloadFileName: filename)
    }

    private func link(url: String, icon: PageIcon, text: String, markdown: Substring) -> ContentButtons.Item? {
        guard let encodedUrl = url.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) else {
            invalid(markdown)
            return nil
        }

        results.externalLink(to: url)
        results.require(icon: icon)
        return .init(icon: icon, filePath: encodedUrl, text: text)
    }

    private func action(event: String, icon: PageIcon, text: String) -> ContentButtons.Item? {
        results.require(icon: icon)
        return .init(icon: icon, filePath: nil, text: text, onClickText: event)
    }
}
