import Foundation

/// 要素
protocol Element {
    /// HTMLを書き出すレンダー
    /// - Returns: HTML
    func render() -> String
}

/// HTML要素
protocol HTMLElement: Element {
    /// タグ名
    var tag: String { get }

    /// 属性
    var attribute: [String: String]? { get set }

    /// タグ内部
    var innerHTML: [Element]? { get set }

    /// 初期化
    init()

    /// 初期化
    /// - Parameters:
    ///   - attribute: 属性
    ///   - innerHTML: タグ内部
    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element])
}

extension HTMLElement {
    /// 初期化のデフォルト実装
    init(_ attribute: [String: String]? = nil, @ElementBuilder innerHTML: () -> [Element]) {
        self.init()
        self.innerHTML = innerHTML()
        self.attribute = attribute
    }

    /// レンダーのデフォルト実装
    func render() -> String {
        // 属性と内部タグが設定されていない場合はオープンタグ
        if attribute == nil, innerHTML == nil {
            return "<\(tag)>"
        }

        // 属性
        let a = attribute?.reduce(into: "") {
            $0 += $1.value.isEmpty ? " \($1.key)" : #" \#($1.key)="\#($1.value)""#
        } ?? ""

        // 内部タグ
        let i = innerHTML?.reduce(into: "") {
            $0 += $1.render()
        } ?? ""

        return "<\(tag)\(a)>\(i)</\(tag)>"
    }
}

// MARK: - resultBuilder
/// HTMLを書き出すresultBuilder
@resultBuilder struct HTMLBuilder {
    static func buildBlock(_ elements: HTMLElement...) -> String {
        elements.reduce(into: "") { $0 += $1.render() }
    }
}

/// 内部タグを取得するresultBuilder
/// HTMLElementをネストさせるために使用
@resultBuilder struct ElementBuilder {
    static func buildBlock(_ elements: Element...) -> [Element] {
        elements
    }
}

// MARK: - 既存のstructの拡張
extension String: Element {
    func render() -> String {
        self
    }
}

extension Int: Element {
    func render() -> String {
        "\(self)"
    }
}

// MARK: - タグ
/// Doctype
struct Doctype: HTMLElement {
    let tag = "!DOCTYPE html"
    var attribute: [String: String]?
    var innerHTML: [Element]?

    // 属性と内部タグを無効にするために初期化とレンダーをオーバーライド
    init() {}
    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element]) {}

    func render() -> String {
        "<!DOCTYPE html>"
    }
}

struct Html: HTMLElement {
    let tag = "html"
    var attribute: [String: String]?
    var innerHTML: [Element]?
}

struct Head: HTMLElement {
    let tag = "head"
    var attribute: [String: String]?
    var innerHTML: [Element]?
}

struct Title: HTMLElement {
    let tag = "title"
    var attribute: [String: String]?
    var innerHTML: [Element]?
}

struct Body: HTMLElement {
    let tag = "body"
    var attribute: [String: String]?
    var innerHTML: [Element]?
}

struct P: HTMLElement {
    let tag = "p"
    var attribute: [String: String]?
    var innerHTML: [Element]?
}

struct Br: HTMLElement {
    let tag = "br"
    var attribute: [String: String]?
    var innerHTML: [Element]?

    // 内部タグを無効にするために初期化とレンダーをオーバーライド
    init() {}
    init(_ attribute: [String: String]?, @ElementBuilder innerHTML: () -> [Element]) {
        self.attribute = attribute
    }

    func render() -> String {
        // 属性
        let a = attribute?.reduce(into: "") {
            $0 += $1.value.isEmpty ? " \($1.key)" : #" \#($1.key)="\#($1.value)""#
        } ?? ""
        return "<\(tag)\(a)>"
    }
}

// MARK: - Test
// 外部で要素を作れる
var p = P { "段落" }
p.attribute = ["data-test1": "333"]
p.attribute?["data-test2"] = "444"

// HTMLの作成
@HTMLBuilder func html() -> String {
    Doctype()
    Html {
        Head {
            Title { "タイトル" }
        }
        Body {
            P(["style": "color:red"]) {
                "テキスト" // String
                p
                1 // Int
            }
            P() // オープンタグ
            P {} // クローズタグ
            Br()
            Br(["data-test3": ""]) { "内部タグは無視される" }
        }
    }
}
print(html())

// <!DOCTYPE html><html><head><title>タイトル</title></head><body><p style="color:red">テキスト<p data-test1="333" data-test2="444">段落</p>1</p><p><p></p><br><br data-test3></body></html>