-- Configuration
local time_tolerance = 0.05 -- 50ms tolerance for grouping notes

local pitch_names = {"C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"}

-- Common chord intervals (relative to root)
local chord_types = {
    {name = "", intervals = {0, 4, 7}}, -- Major
    {name = "m", intervals = {0, 3, 7}}, -- Minor
    {name = "dim", intervals = {0, 3, 6}}, -- Diminished
    {name = "aug", intervals = {0, 4, 8}}, -- Augmented
    {name = "sus2", intervals = {0, 2, 7}}, -- Sus2
    {name = "sus4", intervals = {0, 5, 7}}, -- Sus4
    {name = "5", intervals = {0, 7}}, -- Power chord
    
    {name = "maj7", intervals = {0, 4, 7, 11}}, -- Maj7
    {name = "m7", intervals = {0, 3, 7, 10}}, -- Min7
    {name = "7", intervals = {0, 4, 7, 10}}, -- Dom7
    {name = "dim7", intervals = {0, 3, 6, 9}}, -- Dim7
    {name = "m7b5", intervals = {0, 3, 6, 10}}, -- Half-dim
    {name = "mmaj7", intervals = {0, 3, 7, 11}}, -- Minor maj7
    
    {name = "6", intervals = {0, 4, 7, 9}}, -- Maj6
    {name = "m6", intervals = {0, 3, 7, 9}}, -- Min6
    {name = "add9", intervals = {0, 2, 4, 7}}, -- add9
    {name = "madd9", intervals = {0, 2, 3, 7}}, -- madd9
    {name = "9", intervals = {0, 2, 4, 7, 10}}, -- dom9
    {name = "maj9", intervals = {0, 2, 4, 7, 11}}, -- maj9
    {name = "m9", intervals = {0, 2, 3, 7, 10}}, -- m9
}

function identify_chord(note_pitches)
    if #note_pitches == 0 then return "" end
    if #note_pitches == 1 then return pitch_names[(note_pitches[1] % 12) + 1] end

    local unique_pcs = {}
    local bass_pitch = 128
    for _, p in ipairs(note_pitches) do
        local pc = p % 12
        unique_pcs[pc] = true
        if p < bass_pitch then bass_pitch = p end
    end
    local bass_pc = bass_pitch % 12

    local pcs = {}
    for pc, _ in pairs(unique_pcs) do table.insert(pcs, pc) end
    table.sort(pcs)

    local best_match = nil
    
    for root = 0, 11 do
        -- Transpose pcs relative to root
        local rel_pcs = {}
        for _, pc in ipairs(pcs) do
            table.insert(rel_pcs, (pc - root) % 12)
        end
        table.sort(rel_pcs)
        
        -- Compare with known chords
        for _, ctype in ipairs(chord_types) do
            if #rel_pcs == #ctype.intervals then
                local match = true
                for i = 1, #rel_pcs do
                    if rel_pcs[i] ~= ctype.intervals[i] then
                        match = false
                        break
                    end
                end
                if match then
                    local name = pitch_names[root + 1] .. ctype.name
                    if bass_pc ~= root then
                        name = name .. "/" .. pitch_names[bass_pc + 1]
                    end
                    -- Prefer root position chords if multiple match
                    if not best_match or (bass_pc == root and not best_match.is_root_pos) then
                        best_match = {name = name, is_root_pos = (bass_pc == root)}
                    end
                end
            end
        end
    end
    
    if best_match then return best_match.name end
    
    -- If no exact match, return the notes
    local fallback = ""
    for i, pc in ipairs(pcs) do
        fallback = fallback .. pitch_names[pc + 1]
        if i < #pcs then fallback = fallback .. "," end
    end
    return fallback
end

reaper.Undo_BeginBlock()

local item = reaper.GetSelectedMediaItem(0, 0)
if not item then
    reaper.ShowMessageBox("Please select a MIDI item first.", "Error", 0)
    return
end

local take = reaper.GetActiveTake(item)
if not take or not reaper.TakeIsMIDI(take) then
    reaper.ShowMessageBox("Selected item is not a MIDI item.", "Error", 0)
    return
end

-- Get all notes
local retval, notecnt, ccevtcnt, textsyxevtcnt = reaper.MIDI_CountEvts(take)
local notes = {}

for i = 0, notecnt - 1 do
    local retval, selected, muted, startppqpos, endppqpos, chan, pitch, vel = reaper.MIDI_GetNote(take, i)
    if not muted then
        local start_time = reaper.MIDI_GetProjTimeFromPPQPos(take, startppqpos)
        local end_time = reaper.MIDI_GetProjTimeFromPPQPos(take, endppqpos)
        table.insert(notes, {start_time = start_time, end_time = end_time, pitch = pitch})
    end
end

if #notes == 0 then
    reaper.ShowMessageBox("Selected MIDI item has no active notes.", "Error", 0)
    return
end

table.sort(notes, function(a, b) return a.start_time < b.start_time end)

-- Group notes
local groups = {}
local current_group = nil

for i, note in ipairs(notes) do
    if not current_group then
        current_group = {start_time = note.start_time, end_time = note.end_time, pitches = {note.pitch}}
    else
        if math.abs(note.start_time - current_group.start_time) <= time_tolerance then
            table.insert(current_group.pitches, note.pitch)
            if note.end_time > current_group.end_time then
                current_group.end_time = note.end_time
            end
        else
            table.insert(groups, current_group)
            current_group = {start_time = note.start_time, end_time = note.end_time, pitches = {note.pitch}}
        end
    end
end
if current_group then
    table.insert(groups, current_group)
end

-- Create a new track for chords
local track_idx = reaper.GetMediaTrackInfo_Value(reaper.GetMediaItemTrack(item), "IP_TRACKNUMBER")
reaper.InsertTrackAtIndex(track_idx, true)
local chord_track = reaper.GetTrack(0, track_idx)
reaper.GetSetMediaTrackInfo_String(chord_track, "P_NAME", "Chords Display", true)

-- Create empty items
for _, g in ipairs(groups) do
    local chord_name = identify_chord(g.pitches)
    
    local new_item = reaper.AddMediaItemToTrack(chord_track)
    reaper.SetMediaItemInfo_Value(new_item, "D_POSITION", g.start_time)
    reaper.SetMediaItemInfo_Value(new_item, "D_LENGTH", g.end_time - g.start_time)
    
    -- Set item text (using | to center text vertically and horizontally, scaling it up)
    reaper.GetSetMediaItemInfo_String(new_item, "P_NOTES", "|" .. chord_name, true)
    
    -- Set bright yellow background for high contrast
    local custom_color = reaper.ColorToNative(255, 230, 0) | 0x1000000
    reaper.SetMediaItemInfo_Value(new_item, "I_CUSTOMCOLOR", custom_color)
end

reaper.UpdateArrange()
reaper.Undo_EndBlock("Generate Chord Text Items from MIDI", -1)
