import SwiftMath
import SwiftUI
#if os(macOS)
import AppKit
#else
import UIKit
#endif

@MainActor
struct ChatCodeBlockView: View {
    let block: ChatCodeBlock

    var body: some View {
        VStack(alignment: .leading, spacing: 6) {
            if let language = self.block.language {
                Text(language)
                    .font(OpenClawChatTypography.caption2)
                    .foregroundStyle(.secondary)
            }
            ScrollView(.horizontal, showsIndicators: false) {
                Text(self.attributedCode)
                    .font(OpenClawChatTypography.mono(size: 13, relativeTo: .footnote))
                    .foregroundStyle(OpenClawChatTheme.assistantText)
                    .lineSpacing(2)
                    .textSelection(.enabled)
            }
        }
        .padding(10)
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(
            RoundedRectangle(cornerRadius: 12, style: .continuous)
                .fill(OpenClawChatTheme.subtleCard)
                .overlay(
                    RoundedRectangle(cornerRadius: 12, style: .continuous)
                        .strokeBorder(Color.white.opacity(0.08), lineWidth: 1)))
    }

    private var attributedCode: AttributedString {
        // Open (still-streaming) fences skip highlighting so each delta stays
        // cheap; completed blocks hit the content-keyed highlight cache.
        guard self.block.isComplete else { return AttributedString(self.block.code) }
        return ChatCodeHighlightCache.highlighted(
            code: self.block.code,
            languageId: self.block.language)
    }
}

@MainActor
struct ChatMathBlockView: View {
    let block: ChatMathBlock
    let textColor: Color

    @ScaledMetric(relativeTo: .body) private var fontSize: CGFloat = OpenClawChatTypography.bodySize

    var body: some View {
        if self.block.isComplete,
           ChatMathParseCache.mathList(latex: self.block.latex) != nil
        {
            ScrollView(.horizontal, showsIndicators: false) {
                ChatMathPlatformView(
                    latex: self.block.latex,
                    fontSize: self.fontSize,
                    textColor: self.textColor)
                    .fixedSize()
                    .accessibilityElement(children: .ignore)
                    .accessibilityLabel(self.block.latex)
            }
            .defaultScrollAnchor(.center)
            .frame(maxWidth: .infinity)
            .padding(.vertical, 4)
        } else {
            ChatCodeBlockView(block: ChatCodeBlock(
                language: nil,
                code: self.block.latex,
                isComplete: false))
        }
    }
}

#if os(macOS)
@MainActor
private struct ChatMathPlatformView: NSViewRepresentable {
    let latex: String
    let fontSize: CGFloat
    let textColor: Color

    func makeNSView(context: Context) -> MTMathUILabel {
        MTMathUILabel()
    }

    func updateNSView(_ view: MTMathUILabel, context: Context) {
        self.configure(view)
    }

    private func configure(_ view: MTMathUILabel) {
        view.displayErrorInline = false
        view.labelMode = .display
        view.textAlignment = .center
        view.fontSize = self.fontSize
        view.textColor = NSColor(self.textColor)
        if view.latex != self.latex {
            view.latex = self.latex
        }
    }
}
#else
@MainActor
private struct ChatMathPlatformView: UIViewRepresentable {
    let latex: String
    let fontSize: CGFloat
    let textColor: Color

    func makeUIView(context: Context) -> MTMathUILabel {
        MTMathUILabel()
    }

    func updateUIView(_ view: MTMathUILabel, context: Context) {
        self.configure(view)
    }

    private func configure(_ view: MTMathUILabel) {
        view.displayErrorInline = false
        view.labelMode = .display
        view.textAlignment = .center
        view.fontSize = self.fontSize
        view.textColor = UIColor(self.textColor)
        if view.latex != self.latex {
            view.latex = self.latex
        }
    }
}
#endif

@MainActor
struct ChatMarkdownTableView: View {
    let table: ChatMarkdownTable

    var body: some View {
        ScrollView(.horizontal, showsIndicators: false) {
            Grid(alignment: .topLeading, horizontalSpacing: 14, verticalSpacing: 7) {
                GridRow {
                    ForEach(self.table.header.indices, id: \.self) { column in
                        // One cell per column carries the GFM alignment.
                        self.cell(self.table.header[column], isHeader: true)
                            .gridColumnAlignment(self.columnAlignment(column))
                    }
                }
                Divider()
                ForEach(self.table.rows.indices, id: \.self) { rowIndex in
                    GridRow {
                        ForEach(self.table.rows[rowIndex].indices, id: \.self) { column in
                            self.cell(self.table.rows[rowIndex][column], isHeader: false)
                        }
                    }
                }
            }
        }
        .padding(10)
        .frame(maxWidth: .infinity, alignment: .leading)
        .background(
            RoundedRectangle(cornerRadius: 12, style: .continuous)
                .fill(OpenClawChatTheme.subtleCard)
                .overlay(
                    RoundedRectangle(cornerRadius: 12, style: .continuous)
                        .strokeBorder(Color.white.opacity(0.08), lineWidth: 1)))
    }

    private func cell(_ text: String, isHeader: Bool) -> some View {
        Text(self.inlineMarkdown(text))
            .font(isHeader ? OpenClawChatTypography.footnoteSemiBold : OpenClawChatTypography.footnote)
            .foregroundStyle(OpenClawChatTheme.assistantText)
            .textSelection(.enabled)
    }

    private func inlineMarkdown(_ text: String) -> AttributedString {
        let options = AttributedString.MarkdownParsingOptions(
            interpretedSyntax: .inlineOnlyPreservingWhitespace,
            failurePolicy: .returnPartiallyParsedIfPossible)
        return (try? AttributedString(markdown: text, options: options)) ?? AttributedString(text)
    }

    private func columnAlignment(_ column: Int) -> HorizontalAlignment {
        guard column < self.table.alignments.count else { return .leading }
        switch self.table.alignments[column] {
        case .leading: return .leading
        case .center: return .center
        case .trailing: return .trailing
        }
    }
}

@MainActor
struct ChatMarkdownListView: View {
    let list: ChatMarkdownList
    let context: ChatMarkdownRenderer.Context
    let variant: ChatMarkdownVariant
    let font: Font
    let textColor: Color
    let inlineMathTypography: ChatMarkdownRenderer.InlineMathTypography

    var body: some View {
        Grid(alignment: .topLeading, horizontalSpacing: 8, verticalSpacing: 7) {
            ForEach(self.list.items.indices, id: \.self) { index in
                GridRow(alignment: .top) {
                    self.marker(for: self.list.items[index], at: index)
                        .frame(minWidth: 18, alignment: .trailing)
                        .padding(.top, 1)

                    VStack(alignment: .leading, spacing: 7) {
                        if self.list.items[index].content.isEmpty {
                            ChatMarkdownRenderer.styledText(" ", font: self.font)
                        } else {
                            ForEach(self.list.items[index].content.indices, id: \.self) { contentIndex in
                                self.content(self.list.items[index].content[contentIndex])
                            }
                        }
                    }
                    .frame(maxWidth: .infinity, alignment: .leading)
                    // Grid cells otherwise accept compressed row proposals and ellipsize
                    // wrapped text. Preserve the item's ideal height while still wrapping.
                    .fixedSize(horizontal: false, vertical: true)
                }
            }
        }
        .frame(maxWidth: .infinity, alignment: .leading)
    }

    @ViewBuilder
    private func marker(for item: ChatMarkdownListItem, at index: Int) -> some View {
        let marker = self.list.marker(for: item, at: index)
        HStack(spacing: 4) {
            if let text = marker.text {
                ChatMarkdownRenderer.styledText(text, font: self.font)
                    .foregroundStyle(self.textColor)
                    .monospacedDigit()
                    .accessibilityLabel(self.markerAccessibilityLabel(at: index))
            }
            if let checkbox = marker.checkbox {
                Image(systemName: checkbox == .checked ? "checkmark.square.fill" : "square")
                    .font(self.font)
                    .foregroundStyle(self.textColor)
                    .accessibilityLabel(Text(self.checkboxAccessibilityLabel(checkbox)))
            }
        }
        .accessibilityElement(children: .combine)
    }

    private func checkboxAccessibilityLabel(
        _ checkbox: ChatMarkdownListItem.Checkbox) -> LocalizedStringKey
    {
        switch checkbox {
        case .checked: "Completed"
        case .unchecked: "Pending"
        }
    }

    @ViewBuilder
    private func content(_ content: ChatMarkdownListItemContent) -> some View {
        switch content {
        case let .markdown(markdown):
            self.markdownRenderer(markdown)
        case let .code(code):
            ChatCodeBlockView(block: code)
        case let .list(list):
            self.nestedListView(list)
        }
    }

    func markdownRenderer(_ markdown: String) -> ChatMarkdownRenderer {
        ChatMarkdownRenderer(
            text: markdown,
            context: self.context,
            variant: self.variant,
            font: self.font,
            textColor: self.textColor,
            inlineMathTypography: self.inlineMathTypography)
    }

    func nestedListView(_ list: ChatMarkdownList) -> ChatMarkdownListView {
        ChatMarkdownListView(
            list: list,
            context: self.context,
            variant: self.variant,
            font: self.font,
            textColor: self.textColor,
            inlineMathTypography: self.inlineMathTypography)
    }

    private func markerAccessibilityLabel(at index: Int) -> Text {
        switch self.list.kind {
        case .unordered:
            return Text("List item")
        case let .ordered(start):
            let itemNumber = start + UInt(index)
            return Text("Item") + Text(verbatim: " \(itemNumber)")
        }
    }
}

struct ChatMarkdownListMarker: Equatable {
    let text: String?
    let checkbox: ChatMarkdownListItem.Checkbox?
}

extension ChatMarkdownList {
    func marker(for item: ChatMarkdownListItem, at index: Int) -> ChatMarkdownListMarker {
        let text: String? = switch self.kind {
        case .unordered where item.checkbox != nil:
            nil
        case .unordered:
            "•"
        case let .ordered(start):
            "\(start + UInt(index))."
        }
        return ChatMarkdownListMarker(text: text, checkbox: item.checkbox)
    }
}
