import SwiftUI
import AppKit

private let visibleColumnsKey = "visibleTrackColumns"

private let defaultVisibleColumnIds: Set<String> = ["title", "artist", "album", "genre", "duration"]

private let columnDefinitions: [(id: String, title: String, width: CGFloat, rightAlign: Bool)] = [
    ("title", "Title", 300, false),
    ("artist", "Artist", 200, false),
    ("albumArtist", "Album Artist", 200, false),
    ("album", "Album", 200, false),
    ("composer", "Composer", 150, false),
    ("genre", "Genre", 100, false),
    ("year", "Year", 50, true),
    ("bpm", "BPM", 50, true),
    ("trackNumber", "Track #", 50, true),
    ("discNumber", "Disc #", 50, true),
    ("duration", "Time", 70, true),
    ("playCount", "Plays", 50, true),
    ("lastPlayedAt", "Last Played", 130, false),
    ("rating", "Rating", 60, true),
    ("dateAdded", "Date Added", 130, false),
    ("dateModified", "Date Modified", 130, false),
    ("fileFormat", "Kind", 60, false),
    ("bitrate", "Bit Rate", 80, true),
    ("sampleRate", "Sample Rate", 90, true),
    ("fileSize", "Size", 70, true),
]

private func loadVisibleColumnIds() -> Set<String> {
    if let saved = UserDefaults.standard.stringArray(forKey: visibleColumnsKey) {
        return Set(saved).union(["title"])
    }
    return defaultVisibleColumnIds
}

struct TrackTableView: NSViewRepresentable {
    let tracks: [Track]
    let playingTrackId: Int64?
    let sortColumn: String
    let sortAscending: Bool
    let onSort: (String) -> Void
    let onDoubleClick: (Track) -> Void
    var playlists: [Playlist]
    var lastUsedPlaylistName: String?
    var selectedPlaylist: Playlist?
    var onAddToPlaylist: ((Track, Playlist) -> Void)?
    var onAddToLastPlaylist: ((Track) -> Void)?
    var onRemoveFromPlaylist: ((Track) -> Void)?
    var onReorder: ((Int, Int) -> Void)?
    var scrollToPlayingTrigger: UUID = UUID()

    func makeNSView(context: Context) -> NSScrollView {
        let scrollView = NSScrollView()
        scrollView.hasVerticalScroller = true
        scrollView.autohidesScrollers = true

        let tableView = PlayableTableView()
        tableView.style = .plain
        tableView.usesAlternatingRowBackgroundColors = true
        tableView.allowsMultipleSelection = false
        tableView.rowHeight = 24
        tableView.intercellSpacing = NSSize(width: 10, height: 0)

        let visibleIds = loadVisibleColumnIds()

        let nowPlayingColumn = NSTableColumn(identifier: NSUserInterfaceItemIdentifier("nowPlaying"))
        nowPlayingColumn.title = ""
        nowPlayingColumn.width = 20
        nowPlayingColumn.minWidth = 20
        nowPlayingColumn.maxWidth = 20
        tableView.addTableColumn(nowPlayingColumn)

        for col in columnDefinitions {
            let column = NSTableColumn(identifier: NSUserInterfaceItemIdentifier(col.id))
            column.title = col.title
            column.width = col.width
            column.minWidth = 50
            column.sortDescriptorPrototype = NSSortDescriptor(key: col.id, ascending: true)
            column.isHidden = !visibleIds.contains(col.id)
            if col.rightAlign {
                column.headerCell.alignment = .right
            }
            tableView.addTableColumn(column)
        }

        tableView.sortDescriptors = [NSSortDescriptor(key: sortColumn, ascending: sortAscending)]

        tableView.delegate = context.coordinator
        tableView.dataSource = context.coordinator
        tableView.doubleAction = #selector(Coordinator.handleDoubleClick(_:))
        tableView.target = context.coordinator
        tableView.enterAction = #selector(Coordinator.handleEnterKey(_:))

        context.coordinator.tableView = tableView

        let menu = NSMenu()
        for col in columnDefinitions {
            let item = NSMenuItem(
                title: col.title,
                action: #selector(Coordinator.toggleColumn(_:)),
                keyEquivalent: ""
            )
            item.target = context.coordinator
            item.representedObject = col.id
            item.state = visibleIds.contains(col.id) ? .on : .off
            if col.id == "title" {
                item.isEnabled = false
            }
            menu.addItem(item)
        }
        tableView.headerView?.menu = menu

        scrollView.documentView = tableView

        let rowMenu = NSMenu()
        rowMenu.delegate = context.coordinator
        tableView.menu = rowMenu

        return scrollView
    }

    func updateNSView(_ scrollView: NSScrollView, context: Context) {
        guard let tableView = scrollView.documentView as? NSTableView else { return }

        let tracksChanged = context.coordinator.tracks != tracks
        let playingChanged = context.coordinator.playingTrackId != playingTrackId
        let scrollTriggered = context.coordinator.lastScrollTrigger != scrollToPlayingTrigger

        context.coordinator.parent = self

        let expectedDescriptor = NSSortDescriptor(key: sortColumn, ascending: sortAscending)
        if tableView.sortDescriptors.first != expectedDescriptor {
            tableView.sortDescriptors = [expectedDescriptor]
        }

        if context.coordinator.parent.onReorder != nil {
            if tableView.registeredDraggedTypes.isEmpty || !tableView.registeredDraggedTypes.contains(.string) {
                tableView.registerForDraggedTypes([.string])
                tableView.draggingDestinationFeedbackStyle = .gap
            }
        } else {
            tableView.unregisterDraggedTypes()
        }

        if scrollTriggered {
            context.coordinator.lastScrollTrigger = scrollToPlayingTrigger
            if let playingId = playingTrackId,
               let row = context.coordinator.tracks.firstIndex(where: { $0.id == playingId }) {
                tableView.scrollRowToVisible(row)
                tableView.selectRowIndexes(IndexSet(integer: row), byExtendingSelection: false)
            }
        }

        guard tracksChanged || playingChanged else { return }

        let selectedIds = Set(tableView.selectedRowIndexes.compactMap { idx -> Int64? in
            guard idx < context.coordinator.tracks.count else { return nil }
            return context.coordinator.tracks[idx].id
        })

        context.coordinator.tracks = tracks
        context.coordinator.playingTrackId = playingTrackId

        tableView.reloadData()

        if !selectedIds.isEmpty {
            let newSelection = IndexSet(tracks.enumerated().compactMap { index, track in
                selectedIds.contains(track.id ?? -1) ? index : nil
            })
            tableView.selectRowIndexes(newSelection, byExtendingSelection: false)
        }
    }

    func makeCoordinator() -> Coordinator {
        Coordinator(self)
    }

    final class Coordinator: NSObject, NSTableViewDelegate, NSTableViewDataSource, NSMenuDelegate {
        var parent: TrackTableView
        var tracks: [Track] = []
        var playingTrackId: Int64?
        var lastScrollTrigger: UUID = UUID()
        weak var tableView: NSTableView?

        init(_ parent: TrackTableView) {
            self.parent = parent
        }

        func numberOfRows(in tableView: NSTableView) -> Int {
            tracks.count
        }

        func tableView(_ tableView: NSTableView, viewFor tableColumn: NSTableColumn?, row: Int) -> NSView? {
            guard row < tracks.count else { return nil }
            let track = tracks[row]
            let colId = tableColumn?.identifier.rawValue ?? ""

            if colId == "nowPlaying" {
                let cellId = NSUserInterfaceItemIdentifier("Cell_nowPlaying")
                let cellView: NSTableCellView
                if let existing = tableView.makeView(withIdentifier: cellId, owner: nil) as? NSTableCellView {
                    cellView = existing
                } else {
                    cellView = NSTableCellView()
                    cellView.identifier = cellId
                    let imageView = NSImageView()
                    imageView.translatesAutoresizingMaskIntoConstraints = false
                    imageView.imageScaling = .scaleProportionallyDown
                    cellView.addSubview(imageView)
                    cellView.imageView = imageView
                    NSLayoutConstraint.activate([
                        imageView.centerXAnchor.constraint(equalTo: cellView.centerXAnchor),
                        imageView.centerYAnchor.constraint(equalTo: cellView.centerYAnchor),
                        imageView.widthAnchor.constraint(equalToConstant: 12),
                        imageView.heightAnchor.constraint(equalToConstant: 12),
                    ])
                }
                let isPlaying = track.id == parent.playingTrackId
                if isPlaying {
                    cellView.imageView?.image = NSImage(systemSymbolName: "speaker.fill", accessibilityDescription: "Now Playing")
                    cellView.imageView?.contentTintColor = .secondaryLabelColor
                } else {
                    cellView.imageView?.image = nil
                }
                return cellView
            }

            let cellId = NSUserInterfaceItemIdentifier("Cell_\(colId)")
            let cellView: NSTableCellView
            if let existing = tableView.makeView(withIdentifier: cellId, owner: nil) as? NSTableCellView {
                cellView = existing
            } else {
                cellView = NSTableCellView()
                cellView.identifier = cellId
                let textField = NSTextField(labelWithString: "")
                textField.lineBreakMode = .byTruncatingTail
                textField.font = .systemFont(ofSize: 12)
                textField.translatesAutoresizingMaskIntoConstraints = false
                cellView.addSubview(textField)
                cellView.textField = textField
                NSLayoutConstraint.activate([
                    textField.leadingAnchor.constraint(equalTo: cellView.leadingAnchor),
                    textField.trailingAnchor.constraint(equalTo: cellView.trailingAnchor),
                    textField.centerYAnchor.constraint(equalTo: cellView.centerYAnchor),
                ])
            }
            let cell = cellView.textField!
            let isPlaying = track.id == parent.playingTrackId
            cell.font = isPlaying ? .boldSystemFont(ofSize: 12) : .systemFont(ofSize: 12)
            cell.textColor = .labelColor
            cell.alignment = .left

            switch colId {
            case "title":
                cell.stringValue = track.title
            case "artist":
                cell.stringValue = track.artist
            case "albumArtist":
                cell.stringValue = track.albumArtist
            case "album":
                cell.stringValue = track.album
            case "composer":
                cell.stringValue = track.composer
            case "genre":
                cell.stringValue = track.genre
            case "year":
                cell.stringValue = track.year.map { String($0) } ?? ""
                cell.alignment = .right
            case "bpm":
                cell.stringValue = track.bpm.map { String($0) } ?? ""
                cell.alignment = .right
            case "trackNumber":
                cell.stringValue = track.trackNumber.map { String($0) } ?? ""
                cell.alignment = .right
            case "discNumber":
                cell.stringValue = track.discNumber.map { String($0) } ?? ""
                cell.alignment = .right
            case "duration":
                cell.stringValue = Self.formatDuration(track.duration)
                cell.alignment = .right
            case "playCount":
                cell.stringValue = track.playCount > 0 ? "\(track.playCount)" : ""
                cell.alignment = .right
            case "lastPlayedAt":
                cell.stringValue = track.lastPlayedAt.map { Self.formatDate($0) } ?? ""
            case "rating":
                cell.stringValue = track.rating > 0 ? String(repeating: "★", count: track.rating) : ""
                cell.alignment = .right
            case "dateAdded":
                cell.stringValue = Self.formatDate(track.dateAdded)
            case "dateModified":
                cell.stringValue = Self.formatDate(track.dateModified)
            case "fileFormat":
                cell.stringValue = track.fileFormat.uppercased()
            case "bitrate":
                cell.stringValue = track.bitrate.map { "\($0) kbps" } ?? ""
                cell.alignment = .right
            case "sampleRate":
                cell.stringValue = track.sampleRate.map { Self.formatSampleRate($0) } ?? ""
                cell.alignment = .right
            case "fileSize":
                cell.stringValue = Self.formatFileSize(track.fileSize)
                cell.alignment = .right
            default:
                cell.stringValue = ""
            }

            return cellView
        }

        func tableView(_ tableView: NSTableView, sortDescriptorsDidChange oldDescriptors: [NSSortDescriptor]) {
            guard let sort = tableView.sortDescriptors.first, let key = sort.key else { return }
            guard key != parent.sortColumn || sort.ascending != parent.sortAscending else { return }
            parent.onSort(key)
        }

        @objc func handleDoubleClick(_ sender: NSTableView) {
            let row = sender.clickedRow
            guard row >= 0, row < tracks.count else { return }
            parent.onDoubleClick(tracks[row])
        }

        @objc func handleEnterKey(_ sender: NSTableView) {
            let row = sender.selectedRow
            guard row >= 0, row < tracks.count else { return }
            parent.onDoubleClick(tracks[row])
        }

        // MARK: - Context Menu

        func menuNeedsUpdate(_ menu: NSMenu) {
            menu.removeAllItems()
            guard let tableView, tableView.clickedRow >= 0, tableView.clickedRow < tracks.count else { return }

            if let lastPlaylistName = parent.lastUsedPlaylistName, parent.onAddToLastPlaylist != nil {
                let lastItem = NSMenuItem(
                    title: "Add to \(lastPlaylistName)",
                    action: #selector(addToLastPlaylist(_:)),
                    keyEquivalent: ""
                )
                lastItem.target = self
                menu.addItem(lastItem)
                menu.addItem(.separator())
            }

            if !parent.playlists.isEmpty {
                let submenu = NSMenu()
                for (index, playlist) in parent.playlists.enumerated() {
                    let item = NSMenuItem(
                        title: playlist.name,
                        action: #selector(addToPlaylist(_:)),
                        keyEquivalent: ""
                    )
                    item.target = self
                    item.tag = index
                    submenu.addItem(item)
                }
                let submenuItem = NSMenuItem(title: "Add to Playlist", action: nil, keyEquivalent: "")
                submenuItem.submenu = submenu
                menu.addItem(submenuItem)
            }

            if parent.selectedPlaylist != nil, parent.onRemoveFromPlaylist != nil {
                menu.addItem(.separator())
                let removeItem = NSMenuItem(
                    title: "Remove from Playlist",
                    action: #selector(removeFromPlaylist(_:)),
                    keyEquivalent: ""
                )
                removeItem.target = self
                menu.addItem(removeItem)
            }
        }

        @objc func addToPlaylist(_ sender: NSMenuItem) {
            guard let tableView, tableView.clickedRow >= 0, tableView.clickedRow < tracks.count else { return }
            let track = tracks[tableView.clickedRow]
            let playlist = parent.playlists[sender.tag]
            parent.onAddToPlaylist?(track, playlist)
        }

        @objc func addToLastPlaylist(_ sender: NSMenuItem) {
            guard let tableView, tableView.clickedRow >= 0, tableView.clickedRow < tracks.count else { return }
            let track = tracks[tableView.clickedRow]
            parent.onAddToLastPlaylist?(track)
        }

        @objc func removeFromPlaylist(_ sender: NSMenuItem) {
            guard let tableView, tableView.clickedRow >= 0, tableView.clickedRow < tracks.count else { return }
            let track = tracks[tableView.clickedRow]
            parent.onRemoveFromPlaylist?(track)
        }

        // MARK: - Drag and Drop

        func tableView(_ tableView: NSTableView, pasteboardWriterForRow row: Int) -> (any NSPasteboardWriting)? {
            guard parent.onReorder != nil else { return nil }
            return "\(row)" as NSString
        }

        func tableView(_ tableView: NSTableView, validateDrop info: any NSDraggingInfo, proposedRow row: Int, proposedDropOperation dropOperation: NSTableView.DropOperation) -> NSDragOperation {
            guard parent.onReorder != nil, dropOperation == .above else { return [] }
            return .move
        }

        func tableView(_ tableView: NSTableView, acceptDrop info: any NSDraggingInfo, row: Int, dropOperation: NSTableView.DropOperation) -> Bool {
            guard let onReorder = parent.onReorder else { return false }
            guard let item = info.draggingPasteboard.pasteboardItems?.first,
                  let rowString = item.string(forType: .string),
                  let sourceRow = Int(rowString) else { return false }

            let destination = sourceRow < row ? row - 1 : row
            guard sourceRow != destination else { return false }

            onReorder(sourceRow, destination)
            return true
        }

        @objc func toggleColumn(_ sender: NSMenuItem) {
            guard let colId = sender.representedObject as? String, let tableView else { return }
            guard let column = tableView.tableColumns.first(where: { $0.identifier.rawValue == colId }) else { return }

            column.isHidden.toggle()
            sender.state = column.isHidden ? .off : .on

            let visibleIds = tableView.tableColumns
                .filter { !$0.isHidden }
                .map { $0.identifier.rawValue }
            UserDefaults.standard.set(visibleIds, forKey: visibleColumnsKey)
        }

        static func formatDuration(_ seconds: Double) -> String {
            guard seconds.isFinite, seconds > 0 else { return "0:00" }
            let mins = Int(seconds) / 60
            let secs = Int(seconds) % 60
            return "\(mins):\(String(format: "%02d", secs))"
        }

        static func formatSampleRate(_ rate: Int) -> String {
            if rate % 1000 == 0 {
                return "\(rate / 1000) kHz"
            }
            return String(format: "%.1f kHz", Double(rate) / 1000.0)
        }

        static func formatFileSize(_ bytes: Int64) -> String {
            ByteCountFormatter.string(fromByteCount: bytes, countStyle: .file)
        }

        static func formatDate(_ date: Date) -> String {
            date.formatted(date: .abbreviated, time: .omitted)
        }
    }
}

private final class PlayableTableView: NSTableView {
    var enterAction: Selector?

    override func keyDown(with event: NSEvent) {
        if event.keyCode == 36 || event.keyCode == 76, let enterAction, let target {
            NSApp.sendAction(enterAction, to: target, from: self)
        } else {
            super.keyDown(with: event)
        }
    }
}
