/**
 * Parses StepMania SSC/SM chart files into timed arrow data.
 *
 * SSC format:
 * - Global header with #BPMS, #OFFSET, etc.
 * - Multiple #NOTEDATA sections, each with #STEPSTYPE, #DIFFICULTY, #METER, #NOTES
 * - Notes are comma-separated measures, each measure has rows of 4 chars (LDUR)
 * - '1' = tap, '0' = nothing (we ignore holds/rolls/mines for now)
 */

export type ArrowDirection = 'left' | 'down' | 'up' | 'right'

export interface ChartNote {
  time: number           // seconds from start
  direction: ArrowDirection
}

export interface ChartDifficulty {
  stepsType: string      // 'dance-single', 'dance-double', etc.
  difficulty: string     // 'Beginner', 'Easy', 'Medium', 'Hard', 'Challenge'
  meter: number
  notes: ChartNote[]
}

export interface ParsedChart {
  title: string
  artist: string
  bpm: number
  offset: number         // seconds
  musicFile: string
  difficulties: ChartDifficulty[]
}

const DIR_MAP: ArrowDirection[] = ['left', 'down', 'up', 'right']

export function parseSSC(text: string): ParsedChart {
  const lines = text.split('\n')

  // Parse global header
  const title = getTag(lines, 'TITLE') || 'Unknown'
  const artist = getTag(lines, 'ARTIST') || 'Unknown'
  const musicFile = getTag(lines, 'MUSIC') || ''
  const offset = parseFloat(getTag(lines, 'OFFSET') || '0')

  // Parse BPM (use first BPM value; we don't support BPM changes yet)
  const bpmStr = getTag(lines, 'BPMS') || '0=120'
  const bpm = parseFloat(bpmStr.split('=')[1]) || 120

  // Parse note sections
  const difficulties: ChartDifficulty[] = []
  let i = 0

  while (i < lines.length) {
    if (lines[i].trim() === '#NOTEDATA:;') {
      const section = parseNoteSection(lines, i + 1, bpm, offset)
      if (section && section.stepsType === 'dance-single') {
        difficulties.push(section)
      }
      i++
    } else {
      i++
    }
  }

  // Some charts use SM-style top-level #NOTES blocks inside .ssc files.
  if (difficulties.length === 0) {
    for (let j = 0; j < lines.length; j++) {
      if (lines[j].trim().startsWith('#NOTES:')) {
        const section = parseInlineNotesSection(lines, j, bpm, offset)
        if (section && section.stepsType === 'dance-single') {
          difficulties.push(section)
        }
      }
    }
  }

  return { title, artist, bpm, offset, musicFile, difficulties }
}

/** Also handles .sm format (older, slightly different structure) */
export function parseSM(text: string): ParsedChart {
  // SM format is similar but notes are inline with the header
  // For now, delegate to SSC parser as the note format is the same
  return parseSSC(text)
}

function getTag(lines: string[], tag: string): string | null {
  for (const line of lines) {
    const trimmed = line.trim()
    const prefix = `#${tag}:`
    if (trimmed.startsWith(prefix)) {
      return trimmed.slice(prefix.length).replace(/;$/, '').trim()
    }
  }
  return null
}

function parseNoteSection(
  lines: string[],
  startIdx: number,
  globalBpm: number,
  globalOffset: number,
): ChartDifficulty | null {
  let stepsType = ''
  let difficulty = ''
  let meter = 0
  let notesStartIdx = -1

  for (let i = startIdx; i < lines.length; i++) {
    const line = lines[i].trim()

    if (line.startsWith('#STEPSTYPE:')) {
      stepsType = line.slice('#STEPSTYPE:'.length).replace(/;$/, '').trim()
    } else if (line.startsWith('#DIFFICULTY:')) {
      difficulty = line.slice('#DIFFICULTY:'.length).replace(/;$/, '').trim()
    } else if (line.startsWith('#METER:')) {
      meter = parseInt(line.slice('#METER:'.length).replace(/;$/, ''), 10) || 0
    } else if (line.startsWith('#NOTES:')) {
      notesStartIdx = i + 1
      break
    }
  }

  if (notesStartIdx < 0) return null

  const noteLines = collectNoteLines(lines, notesStartIdx)
  const notes = parseNoteLines(noteLines, globalBpm, globalOffset)

  return { stepsType, difficulty, meter, notes }
}

function parseInlineNotesSection(
  lines: string[],
  startIdx: number,
  globalBpm: number,
  globalOffset: number,
): ChartDifficulty | null {
  const metadata: string[] = []
  let i = startIdx + 1

  for (; i < lines.length && metadata.length < 5; i++) {
    const line = lines[i].trim()
    if (!line || line.startsWith('//')) continue
    if (line === ';' || line.startsWith('#')) return null
    if (/^[0-9MLF]{4,}$/.test(line)) return null
    metadata.push(line.replace(/:$/, '').trim())
  }

  if (metadata.length < 4) return null

  const stepsType = metadata[0]
  const difficulty = metadata[2]
  const meter = parseInt(metadata[3], 10) || 0
  const noteLines = collectNoteLines(lines, i)
  const notes = parseNoteLines(noteLines, globalBpm, globalOffset)

  return { stepsType, difficulty, meter, notes }
}

function collectNoteLines(lines: string[], startIdx: number): string[] {
  const noteLines: string[] = []
  for (let i = startIdx; i < lines.length; i++) {
    const line = lines[i].trim()
    if (line === '#NOTEDATA:;' || (line.startsWith('#') && !line.startsWith('//'))) break
    if (line.startsWith('//')) continue
    if (line === ';') break
    noteLines.push(line)
  }
  return noteLines
}

function parseNoteLines(noteLines: string[], globalBpm: number, globalOffset: number): ChartNote[] {
  const measures: string[][] = []
  let currentMeasure: string[] = []

  for (const line of noteLines) {
    if (line === ',' || line.startsWith(',')) {
      measures.push(currentMeasure)
      currentMeasure = []
    } else if (line.length >= 4 && /^[0-9MLF]+$/.test(line.slice(0, 4))) {
      currentMeasure.push(line)
    }
  }
  if (currentMeasure.length > 0) {
    measures.push(currentMeasure)
  }

  // Convert to timed notes
  const secondsPerBeat = 60 / globalBpm
  const notes: ChartNote[] = []

  for (let m = 0; m < measures.length; m++) {
    const rows = measures[m]
    const rowsPerMeasure = rows.length
    if (rowsPerMeasure === 0) continue

    for (let r = 0; r < rowsPerMeasure; r++) {
      const row = rows[r]
      const beat = m * 4 + (r / rowsPerMeasure) * 4
      const time = globalOffset + beat * secondsPerBeat

      for (let col = 0; col < 4 && col < row.length; col++) {
        if (row[col] === '1') {
          notes.push({ time, direction: DIR_MAP[col] })
        }
      }
    }
  }

  return notes
}

/** Remove simultaneous arrows, keeping only one note per timestamp. */
export function simplifyNotes(notes: ChartNote[]): ChartNote[] {
  const deduped: ChartNote[] = []
  let lastTime = -1
  for (const n of notes) {
    if (n.time !== lastTime) {
      deduped.push(n)
      lastTime = n.time
    }
  }
  return deduped
}

/** Fetch and parse an SSC file from a URL */
export async function loadSSC(url: string): Promise<ParsedChart> {
  const res = await fetch(url)
  const text = await res.text()
  return parseSSC(text)
}
