import { beforeAll, describe, expect, mock, test } from "bun:test"
import { createRoot } from "solid-js"
import type { LineComment } from "./comments"

let createCommentSessionForTest: typeof import("./comments").createCommentSessionForTest

beforeAll(async () => {
  mock.module("@solidjs/router", () => ({
    useNavigate: () => () => undefined,
    useParams: () => ({}),
    useLocation: () => ({}),
    useSearchParams: () => [{}, () => undefined],
  }))
  mock.module("@opencode-ai/ui/context", () => ({
    createSimpleContext: () => ({
      use: () => undefined,
      provider: () => undefined,
    }),
  }))
  const mod = await import("./comments")
  createCommentSessionForTest = mod.createCommentSessionForTest
})

function line(file: string, id: string, time: number): LineComment {
  return {
    id,
    file,
    comment: id,
    time,
    selection: { start: 1, end: 1 },
  }
}

describe("comments session indexing", () => {
  test("keeps file list behavior and aggregate chronological order", () => {
    createRoot((dispose) => {
      const now = Date.now()
      const comments = createCommentSessionForTest({
        "a.ts": [line("a.ts", "a-late", now + 20_000), line("a.ts", "a-early", now + 1_000)],
        "b.ts": [line("b.ts", "b-mid", now + 10_000)],
      })

      expect(comments.list("a.ts").map((item) => item.id)).toEqual(["a-late", "a-early"])
      expect(comments.all().map((item) => item.id)).toEqual(["a-early", "b-mid", "a-late"])

      const next = comments.add({
        file: "b.ts",
        comment: "next",
        selection: { start: 2, end: 2 },
      })

      expect(comments.list("b.ts").at(-1)?.id).toBe(next.id)
      expect(comments.all().map((item) => item.time)).toEqual(
        comments
          .all()
          .map((item) => item.time)
          .slice()
          .sort((a, b) => a - b),
      )

      dispose()
    })
  })

  test("remove updates file and aggregate indexes consistently", () => {
    createRoot((dispose) => {
      const comments = createCommentSessionForTest({
        "a.ts": [line("a.ts", "a1", 10), line("a.ts", "shared", 20)],
        "b.ts": [line("b.ts", "shared", 30)],
      })

      comments.setFocus({ file: "a.ts", id: "shared" })
      comments.setActive({ file: "a.ts", id: "shared" })
      comments.remove("a.ts", "shared")

      expect(comments.list("a.ts").map((item) => item.id)).toEqual(["a1"])
      expect(
        comments
          .all()
          .filter((item) => item.id === "shared")
          .map((item) => item.file),
      ).toEqual(["b.ts"])
      expect(comments.focus()).toBeNull()
      expect(comments.active()).toEqual({ file: "a.ts", id: "shared" })

      dispose()
    })
  })

  test("clear resets file and aggregate indexes plus focus state", () => {
    createRoot((dispose) => {
      const comments = createCommentSessionForTest({
        "a.ts": [line("a.ts", "a1", 10)],
      })

      const next = comments.add({
        file: "b.ts",
        comment: "next",
        selection: { start: 2, end: 2 },
      })

      comments.setActive({ file: "b.ts", id: next.id })
      comments.clear()

      expect(comments.list("a.ts")).toEqual([])
      expect(comments.list("b.ts")).toEqual([])
      expect(comments.all()).toEqual([])
      expect(comments.focus()).toBeNull()
      expect(comments.active()).toBeNull()

      dispose()
    })
  })

  test("remove keeps focus when same comment id exists in another file", () => {
    createRoot((dispose) => {
      const comments = createCommentSessionForTest({
        "a.ts": [line("a.ts", "shared", 10)],
        "b.ts": [line("b.ts", "shared", 20)],
      })

      comments.setFocus({ file: "b.ts", id: "shared" })
      comments.remove("a.ts", "shared")

      expect(comments.focus()).toEqual({ file: "b.ts", id: "shared" })
      expect(comments.list("a.ts")).toEqual([])
      expect(comments.list("b.ts").map((item) => item.id)).toEqual(["shared"])

      dispose()
    })
  })

  test("setFocus and setActive updater callbacks receive current state", () => {
    createRoot((dispose) => {
      const comments = createCommentSessionForTest()

      comments.setFocus({ file: "a.ts", id: "a1" })
      comments.setFocus((current) => {
        expect(current).toEqual({ file: "a.ts", id: "a1" })
        return { file: "b.ts", id: "b1" }
      })

      comments.setActive({ file: "c.ts", id: "c1" })
      comments.setActive((current) => {
        expect(current).toEqual({ file: "c.ts", id: "c1" })
        return null
      })

      expect(comments.focus()).toEqual({ file: "b.ts", id: "b1" })
      expect(comments.active()).toBeNull()

      dispose()
    })
  })

  test("update changes only the targeted comment body", () => {
    createRoot((dispose) => {
      const comments = createCommentSessionForTest({
        "a.ts": [line("a.ts", "a1", 10), line("a.ts", "a2", 20)],
      })

      comments.update("a.ts", "a2", "edited")

      expect(comments.list("a.ts").map((item) => item.comment)).toEqual(["a1", "edited"])

      dispose()
    })
  })

  test("replace swaps comment state and clears focus state", () => {
    createRoot((dispose) => {
      const comments = createCommentSessionForTest({
        "a.ts": [line("a.ts", "a1", 10)],
      })

      comments.setFocus({ file: "a.ts", id: "a1" })
      comments.setActive({ file: "a.ts", id: "a1" })
      comments.replace([line("b.ts", "b1", 30)])

      expect(comments.list("a.ts")).toEqual([])
      expect(comments.list("b.ts").map((item) => item.id)).toEqual(["b1"])
      expect(comments.focus()).toBeNull()
      expect(comments.active()).toBeNull()

      dispose()
    })
  })
})
