export interface LLMCallDebugData {
  callId: string
}

export interface TimelineSection {
  id: string
  type: "test_generation" | "optimization" | "line_profiler" | "refinement" | "adaptive" | "ranking" | "summary"
  title: string
  subtitle?: string
  timestamp: number
  duration?: number
  status: "success" | "failed" | "partial" | "pending"
  model?: string | null
  cost?: number | null
  tokens?: number | null
  content: TimelineSectionContent
  debugData?: LLMCallDebugData
}

export interface TestGroup {
  index: number
  generated?: { code: string; lines: number }
  instrumented?: { code: string; lines: number }
  instrumentedPerf?: { code: string; lines: number }
}

export type TimelineSectionContent =
  | { type: "tests"; testGroups: TestGroup[]; testFramework?: string }
  | { type: "candidate"; code: string; originalCode: string | null; explanation?: string; rank?: number; isBest?: boolean }
  | { type: "refinement"; code: string; parentCode: string | null; explanation?: string; rank?: number; isBest?: boolean }
  | { type: "ranking"; explanation: string; rankings: Array<{ id: string; rank: number; label: string; code: string; isBest: boolean }>; usedForPr: boolean }
  | { type: "summary"; metrics: { totalCost: number; totalTokens: number; totalDuration: number; candidatesCount: number } }

export interface TransformInput {
  calls: Array<{
    id: string
    call_type: string | null
    model_name: string | null
    status: string
    latency_ms: number | null
    llm_cost: number | null
    total_tokens: number | null
    created_at: Date
    context: { call_sequence?: number } | null
  }>
  optimizationCandidates: Array<{
    id: string
    code: string
    explanation?: string
    index: number
  }>
  lineProfilerCandidates: Array<{
    id: string
    code: string
    explanation?: string
    index: number
  }>
  refinementCandidates: Array<{
    id: string
    code: string
    explanation?: string
    parentId: string | null
    index: number
  }>
  adaptiveCandidates: Array<{
    id: string
    code: string
    explanation?: string
    parentId: string | null
    index: number
  }>
  generatedTests: Array<{ code: string; index: number }>
  instrumentedTests: Array<{ code: string; index: number }>
  instrumentedPerfTests: Array<{ code: string; index: number }>
  originalCode: string | null
  testFramework: string | null
  candidateRankMap: Record<string, number>
  bestCandidateId: string | null
  rankingExplanation: string | null
  usedForPr: boolean
}

export function transformToTimelineSections(
  input: TransformInput
): { sections: TimelineSection[]; totalDuration: number } {
  const {
    calls,
    optimizationCandidates,
    lineProfilerCandidates,
    refinementCandidates,
    adaptiveCandidates,
    generatedTests,
    instrumentedTests,
    instrumentedPerfTests,
    originalCode,
    testFramework,
    candidateRankMap,
    bestCandidateId,
    rankingExplanation,
    usedForPr,
  } = input

  if (calls.length === 0) {
    return { sections: [], totalDuration: 0 }
  }

  // Pre-compute timestamps to avoid duplicate new Date() calls
  const timestampMap = new Map<string, number>()
  for (const call of calls) {
    timestampMap.set(call.id, new Date(call.created_at).getTime())
  }

  // Calculate timeline duration
  let minTime = Infinity
  let maxTime = -Infinity
  let maxLatency = 0

  for (const call of calls) {
    const timestamp = timestampMap.get(call.id)!
    if (timestamp < minTime) minTime = timestamp
    if (timestamp > maxTime) maxTime = timestamp

    const latency = call.latency_ms ?? 0
    if (latency > maxLatency) maxLatency = latency
  }

  const totalDuration = maxTime - minTime + maxLatency

  const sections: TimelineSection[] = []

  // Build test groups
  const maxTestIndex = Math.max(
    generatedTests.length,
    instrumentedTests.length,
    instrumentedPerfTests.length
  )

  const genMap = new Map(generatedTests.map(t => [t.index, t]))
  const instrMap = new Map(instrumentedTests.map(t => [t.index, t]))
  const instrPerfMap = new Map(instrumentedPerfTests.map(t => [t.index, t]))

  const testGroups: TestGroup[] = []
  for (let i = 1; i <= maxTestIndex; i++) {
    const generated = genMap.get(i)
    const instrumented = instrMap.get(i)
    const instrumentedPerf = instrPerfMap.get(i)

    if (generated || instrumented || instrumentedPerf) {
      testGroups.push({
        index: i,
        generated: generated
          ? { code: generated.code, lines: generated.code.split("\n").length }
          : undefined,
        instrumented: instrumented
          ? { code: instrumented.code, lines: instrumented.code.split("\n").length }
          : undefined,
        instrumentedPerf: instrumentedPerf
          ? { code: instrumentedPerf.code, lines: instrumentedPerf.code.split("\n").length }
          : undefined,
      })
    }
  }

  // Process test generation calls
  const testCalls = calls.filter(c => c.call_type === "test_generation")

  if (testCalls.length > 0 || testGroups.length > 0) {
    const firstTestCall = testCalls[0]
    const firstTimestamp = firstTestCall
      ? timestampMap.get(firstTestCall.id)! - minTime
      : 0

    const totalTestDuration = testCalls.reduce((sum, c) => sum + (c.latency_ms ?? 0), 0)
    const totalTestCost = testCalls.reduce((sum, c) => sum + (c.llm_cost ?? 0), 0)
    const totalTestTokens = testCalls.reduce((sum, c) => sum + (c.total_tokens ?? 0), 0)

    const allSuccess = testCalls.length === 0 || testCalls.every(c => c.status === "success")
    const anyFailed = testCalls.some(c => c.status === "failed")

    const subtitle = testFramework
      ? `${testGroups.length} test${testGroups.length > 1 ? "s" : ""} using ${testFramework}`
      : `${testGroups.length} test${testGroups.length > 1 ? "s" : ""} generated`

    sections.push({
      id: firstTestCall ? `tests-${firstTestCall.id}` : "tests",
      type: "test_generation",
      title: "Test Generation",
      subtitle,
      timestamp: firstTimestamp,
      duration: totalTestDuration,
      status: allSuccess ? "success" : anyFailed ? "failed" : "partial",
      model: firstTestCall?.model_name ?? null,
      cost: totalTestCost,
      tokens: totalTestTokens,
      content: {
        type: "tests",
        testGroups,
        testFramework: testFramework ?? undefined,
      },
      debugData: firstTestCall ? { callId: firstTestCall.id } : undefined,
    })
  }

  const allCandidates = [...optimizationCandidates, ...lineProfilerCandidates, ...refinementCandidates, ...adaptiveCandidates]
  const allCandidatesById = new Map(allCandidates.map(c => [c.id, c]))

  // Pre-compute ranking data (identical for all ranking calls)
  const precomputedRankings = Object.entries(candidateRankMap)
    .sort(([, a], [, b]) => a - b)
    .map(([id]) => {
      const cand = allCandidatesById.get(id)
      if (!cand) return null

      const source = (cand as { source?: string }).source
      let prefix: string
      if (source === "ADAPTIVE") {
        prefix = "Adaptive"
      } else if (source === "REFINE") {
        prefix = "Refinement"
      } else if (source === "OPTIMIZE_LP") {
        prefix = "LP Candidate"
      } else {
        prefix = "Candidate"
      }

      return {
        id,
        rank: 0,
        label: `${prefix} ${cand.index}`,
        code: cand.code,
        isBest: false,
      }
    })
    .filter((r): r is NonNullable<typeof r> => r !== null)
    .map((r, index) => ({ ...r, rank: index + 1, isBest: index === 0 }))

  const callIndexByType = new Map<string, number>()
  for (const call of calls) {
    const timestamp = timestampMap.get(call.id)! - minTime
    const callType = call.call_type || "unknown"
    const typeIndex = callIndexByType.get(callType) ?? 0
    callIndexByType.set(callType, typeIndex + 1)

    if (callType === "optimization") {
      const optIndex = typeIndex
      const candidate = optimizationCandidates[optIndex]
      if (candidate) {
        const rank = candidateRankMap[candidate.id]
        sections.push({
          id: call.id,
          type: "optimization",
          title: `Optimization Candidate ${candidate.index}`,
          timestamp,
          duration: call.latency_ms ?? undefined,
          status: call.status === "success" ? "success" : call.status === "failed" ? "failed" : "partial",
          model: call.model_name,
          cost: call.llm_cost,
          tokens: call.total_tokens,
          content: {
            type: "candidate",
            code: candidate.code,
            originalCode,
            explanation: candidate.explanation,
            rank,
            isBest: candidate.id === bestCandidateId,
          },
          debugData: { callId: call.id },
        })
      }
    } else if (callType === "line_profiler") {
      const lpIndex = typeIndex
      const candidate = lineProfilerCandidates[lpIndex]
      if (candidate) {
        const rank = candidateRankMap[candidate.id]
        sections.push({
          id: call.id,
          type: "line_profiler",
          title: `Line Profiler Candidate ${candidate.index}`,
          subtitle: "Guided by profiling data",
          timestamp,
          duration: call.latency_ms ?? undefined,
          status: call.status === "success" ? "success" : call.status === "failed" ? "failed" : "partial",
          model: call.model_name,
          cost: call.llm_cost,
          tokens: call.total_tokens,
          content: {
            type: "candidate",
            code: candidate.code,
            originalCode,
            explanation: candidate.explanation,
            rank,
            isBest: candidate.id === bestCandidateId,
          },
          debugData: { callId: call.id },
        })
      }
    } else if (callType === "refinement") {
      const refIndex = typeIndex
      const candidate = refinementCandidates[refIndex]
      if (candidate) {
        const rank = candidateRankMap[candidate.id]
        const parentCandidate = candidate.parentId
          ? allCandidatesById.get(candidate.parentId)
          : undefined

        let parentLabel: string | undefined
        if (parentCandidate) {
          const source = (parentCandidate as { source?: string }).source
          parentLabel = source === "REFINE"
            ? `From Refinement ${parentCandidate.index}`
            : `From Candidate ${parentCandidate.index}`
        }
        sections.push({
          id: call.id,
          type: "refinement",
          title: `Refinement ${candidate.index}`,
          subtitle: parentLabel,
          timestamp,
          duration: call.latency_ms ?? undefined,
          status: call.status === "success" ? "success" : call.status === "failed" ? "failed" : "partial",
          model: call.model_name,
          cost: call.llm_cost,
          tokens: call.total_tokens,
          content: {
            type: "refinement",
            code: candidate.code,
            parentCode: parentCandidate?.code ?? originalCode,
            explanation: candidate.explanation,
            rank,
            isBest: candidate.id === bestCandidateId,
          },
          debugData: { callId: call.id },
        })
      }
    } else if (callType === "adaptive_optimize") {
      const adaptiveIndex = typeIndex
      const candidate = adaptiveCandidates[adaptiveIndex]
      if (candidate) {
        const rank = candidateRankMap[candidate.id]
        const parentCandidate = candidate.parentId
          ? allCandidatesById.get(candidate.parentId)
          : undefined

        let parentLabel: string | undefined
        if (parentCandidate) {
          const source = (parentCandidate as { source?: string }).source
          if (source === "ADAPTIVE") {
            parentLabel = `From Adaptive ${parentCandidate.index}`
          } else if (source === "REFINE") {
            parentLabel = `From Refinement ${parentCandidate.index}`
          } else {
            parentLabel = `From Candidate ${parentCandidate.index}`
          }
        }
        sections.push({
          id: call.id,
          type: "adaptive",
          title: `Adaptive Optimization ${candidate.index}`,
          subtitle: parentLabel ?? "Informed by full optimization history",
          timestamp,
          duration: call.latency_ms ?? undefined,
          status: call.status === "success" ? "success" : call.status === "failed" ? "failed" : "partial",
          model: call.model_name,
          cost: call.llm_cost,
          tokens: call.total_tokens,
          content: {
            type: "refinement",
            code: candidate.code,
            parentCode: parentCandidate?.code ?? originalCode,
            explanation: candidate.explanation,
            rank,
            isBest: candidate.id === bestCandidateId,
          },
          debugData: { callId: call.id },
        })
      }
    } else if (callType === "ranking") {
      sections.push({
        id: call.id,
        type: "ranking",
        title: "Candidate Ranking",
        subtitle: "Selecting the best optimization",
        timestamp,
        duration: call.latency_ms ?? undefined,
        status: call.status === "success" ? "success" : call.status === "failed" ? "failed" : "partial",
        model: call.model_name,
        cost: call.llm_cost,
        tokens: call.total_tokens,
        content: {
          type: "ranking",
          explanation: rankingExplanation ?? "",
          rankings: precomputedRankings,
          usedForPr,
        },
        debugData: { callId: call.id },
      })
    }
  }

  // Fallback: create sections for adaptive candidates not matched to an LLM call
  // (the backend currently logs adaptive LLM calls with an empty call_type)
  const matchedAdaptiveCount = (callIndexByType.get("adaptive_optimize") ?? 0)
  for (let i = matchedAdaptiveCount; i < adaptiveCandidates.length; i++) {
    const candidate = adaptiveCandidates[i]
    const rank = candidateRankMap[candidate.id]
    const parentCandidate = candidate.parentId
      ? allCandidatesById.get(candidate.parentId)
      : undefined

    let parentLabel: string | undefined
    if (parentCandidate) {
      const source = (parentCandidate as { source?: string }).source
      if (source === "ADAPTIVE") {
        parentLabel = `From Adaptive ${parentCandidate.index}`
      } else if (source === "REFINE") {
        parentLabel = `From Refinement ${parentCandidate.index}`
      } else {
        parentLabel = `From Candidate ${parentCandidate.index}`
      }
    }
    sections.push({
      id: `adaptive-${candidate.id}`,
      type: "adaptive",
      title: `Adaptive Optimization ${candidate.index}`,
      subtitle: parentLabel ?? "Informed by full optimization history",
      timestamp: totalDuration,
      status: "success",
      model: null,
      cost: null,
      tokens: null,
      content: {
        type: "refinement",
        code: candidate.code,
        parentCode: parentCandidate?.code ?? originalCode,
        explanation: candidate.explanation,
        rank,
        isBest: candidate.id === bestCandidateId,
      },
    })
  }

  const typeOrder: Record<string, number> = {
    test_generation: 0,
    optimization: 1,
    line_profiler: 2,
    refinement: 3,
    adaptive: 4,
    ranking: 5,
    summary: 6,
  }

  const candidateTypeSet = new Set(["optimization", "line_profiler", "refinement", "adaptive"])
  const sectionSortIndex = new Map(
    sections.map(s => [s, parseInt(s.title.match(/\d+$/)?.[0] ?? "0", 10)])
  )

  sections.sort((a, b) => {
    const orderA = typeOrder[a.type] ?? 99
    const orderB = typeOrder[b.type] ?? 99
    if (orderA !== orderB) return orderA - orderB
    if (candidateTypeSet.has(a.type)) {
      return sectionSortIndex.get(a)! - sectionSortIndex.get(b)!
    }
    return a.timestamp - b.timestamp
  })

  return { sections, totalDuration }
}