//
//  MarkdownContentView.swift
//  oAI
//
//  Renders markdown content with syntax-highlighted code blocks
//
// SPDX-License-Identifier: AGPL-3.0-or-later
// Copyright (C) 2026 Rune Olsen
//
// This file is part of oAI.
//
// oAI is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// oAI is distributed in the hope that it will be useful, but WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
// or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Affero General
// Public License for more details.
//
// You should have received a copy of the GNU Affero General Public
// License along with oAI. If not, see <https://www.gnu.org/licenses/>.


import SwiftUI
import MarkdownUI
#if canImport(AppKit)
import AppKit
#endif

struct MarkdownContentView: View {
    let content: String
    let fontSize: Double

    var body: some View {
        let segments = parseSegments(content)
        VStack(alignment: .leading, spacing: 12) {
            ForEach(segments.indices, id: \.self) { index in
                switch segments[index] {
                case .text(let text):
                    if !text.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
                        markdownText(text)
                    }
                case .codeBlock(let language, let code):
                    CodeBlockView(language: language, code: code, fontSize: fontSize)
                case .table(let tableText):
                    TableView(content: tableText, fontSize: fontSize)
                }
            }
        }
    }

    @ViewBuilder
    private func markdownText(_ text: String) -> some View {
        Markdown(text)
            .markdownTextStyle {
                FontSize(fontSize)
                ForegroundColor(.primary)
            }
            .markdownBlockStyle(\.paragraph) { configuration in
                configuration.label
                    .markdownMargin(top: 0, bottom: 8)
            }
            .textSelection(.enabled)
    }

    // MARK: - Parsing

    enum Segment {
        case text(String)
        case codeBlock(language: String?, code: String)
        case table(String)
    }

    private func parseSegments(_ content: String) -> [Segment] {
        var segments: [Segment] = []
        let lines = content.components(separatedBy: "\n")
        var currentText = ""
        var inCodeBlock = false
        var codeLanguage: String? = nil
        var codeContent = ""
        var inTable = false
        var tableLines: [String] = []

        for line in lines {
            if !inCodeBlock && line.hasPrefix("```") {
                // Start of code block
                if inTable {
                    segments.append(.table(tableLines.joined(separator: "\n")))
                    tableLines = []
                    inTable = false
                }
                if !currentText.isEmpty {
                    segments.append(.text(currentText))
                    currentText = ""
                }
                inCodeBlock = true
                let lang = String(line.dropFirst(3)).trimmingCharacters(in: .whitespaces)
                codeLanguage = lang.isEmpty ? nil : lang
                codeContent = ""
            } else if inCodeBlock && line.hasPrefix("```") {
                // End of code block
                if codeContent.hasSuffix("\n") {
                    codeContent = String(codeContent.dropLast())
                }
                segments.append(.codeBlock(language: codeLanguage, code: codeContent))
                inCodeBlock = false
                codeLanguage = nil
                codeContent = ""
            } else if inCodeBlock {
                codeContent += line + "\n"
            } else if line.contains("|") && (line.hasPrefix("|") || line.filter({ $0 == "|" }).count >= 2) {
                // Markdown table line
                if !inTable {
                    // Start of table
                    if !currentText.isEmpty {
                        segments.append(.text(currentText))
                        currentText = ""
                    }
                    inTable = true
                }
                tableLines.append(line)
            } else {
                // Regular text
                if inTable {
                    // End of table
                    segments.append(.table(tableLines.joined(separator: "\n")))
                    tableLines = []
                    inTable = false
                }
                currentText += line + "\n"
            }
        }

        // Handle unclosed code block
        if inCodeBlock {
            if codeContent.hasSuffix("\n") {
                codeContent = String(codeContent.dropLast())
            }
            segments.append(.codeBlock(language: codeLanguage, code: codeContent))
        }

        // Handle unclosed table
        if inTable {
            segments.append(.table(tableLines.joined(separator: "\n")))
        }

        // Remaining text
        if !currentText.isEmpty {
            if currentText.hasSuffix("\n") {
                currentText = String(currentText.dropLast())
            }
            if !currentText.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
                segments.append(.text(currentText))
            }
        }

        return segments
    }
}

// MARK: - Table View

struct TableView: View {
    let content: String
    let fontSize: Double

    private struct TableData {
        let headers: [String]
        let alignments: [TextAlignment]
        let rows: [[String]]
    }

    private var tableData: TableData {
        parseTable(content)
    }

    var body: some View {
        let data = tableData

        guard !data.headers.isEmpty else {
            return AnyView(EmptyView())
        }

        return AnyView(
            VStack(alignment: .leading, spacing: 0) {
                // Headers
                HStack(spacing: 0) {
                    ForEach(data.headers.indices, id: \.self) { index in
                        if index < data.headers.count {
                            Text(data.headers[index].trimmingCharacters(in: .whitespaces))
                                .font(.system(size: fontSize, weight: .semibold))
                                .foregroundColor(.oaiPrimary)
                                .frame(maxWidth: .infinity, alignment: alignmentFor(
                                    index < data.alignments.count ? data.alignments[index] : .leading
                                ))
                                .padding(8)
                                .background(Color.oaiSecondary.opacity(0.1))

                            if index < data.headers.count - 1 {
                                Divider()
                            }
                        }
                    }
                }
                .overlay(
                    Rectangle()
                        .stroke(Color.oaiSecondary.opacity(0.3), lineWidth: 1)
                )

                // Rows
                ForEach(data.rows.indices, id: \.self) { rowIndex in
                    if rowIndex < data.rows.count {
                        HStack(spacing: 0) {
                            ForEach(0..<data.headers.count, id: \.self) { colIndex in
                                let cellContent = colIndex < data.rows[rowIndex].count ? data.rows[rowIndex][colIndex] : ""
                                let alignment = colIndex < data.alignments.count ? data.alignments[colIndex] : .leading

                                Text(cellContent.trimmingCharacters(in: .whitespaces))
                                    .font(.system(size: fontSize))
                                    .foregroundColor(.oaiPrimary)
                                    .frame(maxWidth: .infinity, alignment: alignmentFor(alignment))
                                    .padding(8)

                                if colIndex < data.headers.count - 1 {
                                    Divider()
                                }
                            }
                        }
                        .background(rowIndex % 2 == 0 ? Color.clear : Color.oaiSecondary.opacity(0.05))
                        .overlay(
                            Rectangle()
                                .stroke(Color.oaiSecondary.opacity(0.3), lineWidth: 1)
                        )
                    }
                }
            }
            .cornerRadius(8)
            .overlay(
                RoundedRectangle(cornerRadius: 8)
                    .strokeBorder(Color.oaiSecondary.opacity(0.3), lineWidth: 1)
            )
        )
    }

    private func alignmentFor(_ textAlignment: TextAlignment) -> Alignment {
        switch textAlignment {
        case .leading: return .leading
        case .center: return .center
        case .trailing: return .trailing
        }
    }

    private func parseTable(_ content: String) -> TableData {
        let lines = content.components(separatedBy: "\n").filter { !$0.isEmpty }
        guard lines.count >= 2 else {
            return TableData(headers: [], alignments: [], rows: [])
        }

        // Parse headers (first line)
        let headers = lines[0].components(separatedBy: "|")
            .map { $0.trimmingCharacters(in: .whitespaces) }
            .filter { !$0.isEmpty }

        // Parse alignment row (second line with dashes)
        let alignmentLine = lines[1]
        let alignments = alignmentLine.components(separatedBy: "|")
            .map { $0.trimmingCharacters(in: .whitespaces) }
            .filter { !$0.isEmpty }
            .map { cell -> TextAlignment in
                let trimmed = cell.trimmingCharacters(in: .whitespaces)
                if trimmed.hasPrefix(":") && trimmed.hasSuffix(":") {
                    return .center
                } else if trimmed.hasSuffix(":") {
                    return .trailing
                } else {
                    return .leading
                }
            }

        // Parse data rows (remaining lines)
        let rows = lines.dropFirst(2).map { line in
            line.components(separatedBy: "|")
                .map { $0.trimmingCharacters(in: .whitespaces) }
                .filter { !$0.isEmpty }
        }

        return TableData(headers: headers, alignments: alignments, rows: rows)
    }
}

// MARK: - Code Block View

struct CodeBlockView: View {
    let language: String?
    let code: String
    let fontSize: Double

    @State private var copied = false

    var body: some View {
        VStack(alignment: .leading, spacing: 0) {
            // Header bar with language label and copy button
            HStack {
                if let lang = language, !lang.isEmpty {
                    Text(lang)
                        .font(.system(size: 11, weight: .medium, design: .monospaced))
                        .foregroundColor(Color(hex: "#888888"))
                }
                Spacer()
                Button(action: copyCode) {
                    HStack(spacing: 4) {
                        Image(systemName: copied ? "checkmark" : "doc.on.doc")
                            .font(.system(size: 11))
                        if copied {
                            Text("Copied!")
                                .font(.system(size: 11))
                        }
                    }
                    .foregroundColor(copied ? .green : Color(hex: "#888888"))
                }
                .buttonStyle(.plain)
            }
            .padding(.horizontal, 12)
            .padding(.vertical, 6)
            .background(Color(hex: "#2d2d2d"))

            // Code content
            ScrollView(.horizontal, showsIndicators: true) {
                Text(SyntaxHighlighter.highlight(code: code, language: language))
                    .font(.system(size: fontSize - 1, design: .monospaced))
                    .textSelection(.enabled)
                    .padding(12)
            }
        }
        .background(Color(hex: "#1e1e1e"))
        .cornerRadius(8)
        .overlay(
            RoundedRectangle(cornerRadius: 8)
                .strokeBorder(Color(hex: "#3e3e3e"), lineWidth: 1)
        )
    }

    private func copyCode() {
        #if os(macOS)
        let pasteboard = NSPasteboard.general
        pasteboard.clearContents()
        pasteboard.setString(code, forType: .string)
        #endif
        withAnimation {
            copied = true
        }
        DispatchQueue.main.asyncAfter(deadline: .now() + 1.5) {
            withAnimation {
                copied = false
            }
        }
    }
}
