import "@testing-library/jest-dom/vitest"
import { cleanup, fireEvent, render, screen, waitFor } from "@testing-library/react"
import { afterEach, beforeEach, describe, expect, it } from "vitest"
import { App } from "../src/app/App"
import {
  defaultDatasetId,
  defaultDocumentId,
  defaultResourcePath,
  getCurrentDocument,
  getCurrentRenderResult,
  setProjectPersistenceForTests,
  useWorkbenchStore
} from "../src/app/store/workbenchStore"

describe("workbench UI shell", () => {
  beforeEach(() => {
    setProjectPersistenceForTests(null)
    useWorkbenchStore.getState().resetDemo()
  })

  afterEach(() => {
    cleanup()
  })

  it("starts with the demo entry template and dataset selected", () => {
    const state = useWorkbenchStore.getState()

    expect(state.selectedDocumentId).toBe(defaultDocumentId)
    expect(state.currentDatasetId).toBe(defaultDatasetId)
    expect(state.selectedResourcePath).toBe(defaultResourcePath)
  })

  it("renders the resource tree from the project snapshot", () => {
    render(<App />)

    expect(screen.getByTestId("resource-panel")).toHaveTextContent("main.json")
    expect(screen.getByTestId("resource-panel")).toHaveTextContent("common-footer.json")
    expect(screen.getByTestId("resource-panel")).toHaveTextContent("default.json")
  })

  it("switches inspector tabs", () => {
    render(<App />)

    fireEvent.click(screen.getByRole("button", { name: "预览" }))
    expect(useWorkbenchStore.getState().rightPanelTab).toBe("preview")
    expect(screen.getByTestId("preview-tab")).toHaveTextContent("张三")

    fireEvent.click(screen.getByRole("button", { name: "引用" }))
    expect(useWorkbenchStore.getState().rightPanelTab).toBe("references")
    expect(screen.getByTestId("references-tab")).toHaveTextContent("missing fragment_signature")
  })

  it("generates preview output from the existing renderer", () => {
    const state = useWorkbenchStore.getState()
    const result = getCurrentRenderResult(state)

    expect(result.output).toContain("张三")
    expect(result.output).toContain("- 产品 A: 99")
    expect(result.errors).toContainEqual(
      expect.objectContaining({ code: "MISSING_FRAGMENT", fragmentId: "fragment_signature" })
    )
  })

  it("shows missing reference status", () => {
    render(<App />)

    expect(screen.getByText("missing refs: 1")).toBeInTheDocument()
  })

  it("opens a project through the persistence adapter", async () => {
    const diskSnapshot = {
      ...useWorkbenchStore.getState().snapshot,
      project: {
        ...useWorkbenchStore.getState().snapshot.project,
        name: "Disk Project",
        paths: {
          ...useWorkbenchStore.getState().snapshot.project.paths,
          root: "D:/DiskProject"
        }
      }
    }
    setProjectPersistenceForTests({
      isAvailable: () => true,
      openProjectFromDisk: async () => diskSnapshot,
      saveProjectChanges: async () => diskSnapshot
    })
    useWorkbenchStore.getState().resetDemo()
    render(<App />)

    fireEvent.click(screen.getByRole("button", { name: "Open project" }))

    await waitFor(() => {
      const state = useWorkbenchStore.getState()
      expect(state.projectOpenStatus).toBe("open")
      expect(state.snapshot.project.name).toBe("Disk Project")
      expect(state.dirtyDocumentIds).toEqual([])
    })
    expect(screen.getByText("Disk Project")).toBeInTheDocument()
  })

  it("creates a fragment from the resource panel and selects it", () => {
    render(<App />)

    fireEvent.click(screen.getByRole("button", { name: "Create fragment" }))

    const state = useWorkbenchStore.getState()
    expect(state.snapshot.fragments.fragment_2).toMatchObject({
      kind: "fragment",
      name: "Fragment 2"
    })
    expect(state.selectedDocumentId).toBe("fragment_2")
    expect(state.selectedResourcePath).toBe("fragments/fragment-2.json")
    expect(state.saveStatus).toBe("dirty")
    expect(screen.getByTestId("resource-panel")).toHaveTextContent("fragment-2.json")
  })

  it("saves dirty fragment changes through the persistence adapter", async () => {
    const savedDirtyIds: string[][] = []
    setProjectPersistenceForTests({
      isAvailable: () => true,
      openProjectFromDisk: async () => useWorkbenchStore.getState().snapshot,
      saveProjectChanges: async (snapshot, dirtyDocumentIds) => {
        savedDirtyIds.push(dirtyDocumentIds)
        return snapshot
      }
    })
    useWorkbenchStore.getState().resetDemo()
    render(<App />)

    fireEvent.click(screen.getByRole("button", { name: "Create fragment" }))
    fireEvent.click(screen.getByRole("button", { name: "Save project" }))

    await waitFor(() => {
      const state = useWorkbenchStore.getState()
      expect(savedDirtyIds).toEqual([["fragment_2"]])
      expect(state.saveStatus).toBe("saved")
      expect(state.dirtyDocumentIds).toEqual([])
    })
  })

  it("shows persistence save errors", async () => {
    setProjectPersistenceForTests({
      isAvailable: () => true,
      openProjectFromDisk: async () => useWorkbenchStore.getState().snapshot,
      saveProjectChanges: async () => {
        throw new Error("disk full")
      }
    })
    useWorkbenchStore.getState().resetDemo()
    render(<App />)

    fireEvent.click(screen.getByRole("button", { name: "Create fragment" }))
    fireEvent.click(screen.getByRole("button", { name: "Save project" }))

    await waitFor(() => {
      const state = useWorkbenchStore.getState()
      expect(state.saveStatus).toBe("error")
      expect(state.lastSaveError).toBe("disk full")
    })
    expect(screen.getByText("disk full")).toBeInTheDocument()
  })

  it("opens a referenced fragment from the references panel", () => {
    useWorkbenchStore.getState().setRightPanelTab("references")
    render(<App />)

    fireEvent.click(screen.getByRole("button", { name: "Open fragment_common_footer" }))

    const state = useWorkbenchStore.getState()
    expect(state.selectedDocumentId).toBe("fragment_common_footer")
    expect(state.selectedResourcePath).toBe("fragments/common-footer.json")
    expect(getCurrentDocument(state).kind).toBe("fragment")
  })

  it("expands the selected fragment reference into editable blocks", async () => {
    useWorkbenchStore.getState().setRightPanelTab("references")
    useWorkbenchStore.getState().setSelectedBlockId("block_footer")
    render(<App />)

    fireEvent.click(screen.getByRole("button", { name: "Expand ref" }))

    await waitFor(() => {
      const state = useWorkbenchStore.getState()
      const outgoing = state.snapshot.referenceIndex.outgoing.template_main?.map((item) => item.toFragmentId) ?? []
      expect(outgoing).not.toContain("fragment_common_footer")
      expect(JSON.stringify(state.snapshot.templates.template_main?.children)).not.toContain("block_footer")
      expect(state.saveStatus).toBe("dirty")
      expect(screen.getByTestId("blockflow-editor")).not.toHaveTextContent("fragment_common_footer")
    })
  })

  it("updates the current dataset from valid JSON and refreshes preview data", async () => {
    useWorkbenchStore.getState().setRightPanelTab("data")
    render(<App />)

    fireEvent.change(screen.getByLabelText("Dataset JSON"), {
      target: {
        value: JSON.stringify(
          {
            user: { name: "Ada", isVip: false },
            items: []
          },
          null,
          2
        )
      }
    })

    await waitFor(() => {
      const state = useWorkbenchStore.getState()
      expect(state.dataEditorStatus).toBe("valid")
      expect(getCurrentRenderResult(state).output).toContain("Ada")
      expect(state.saveStatus).toBe("dirty")
    })
  })

  it("keeps the last valid dataset when JSON editing is invalid", async () => {
    useWorkbenchStore.getState().setRightPanelTab("data")
    render(<App />)

    fireEvent.change(screen.getByLabelText("Dataset JSON"), {
      target: { value: "{" }
    })

    await waitFor(() => {
      const state = useWorkbenchStore.getState()
      expect(state.dataEditorStatus).toBe("invalid")
      expect(state.snapshot.datasets[state.currentDatasetId]?.data.user).toEqual(
        expect.objectContaining({ name: "张三" })
      )
    })
    expect(screen.getByTestId("data-tab")).toHaveTextContent("Expected")
  })

  it("renders the selected block preview", () => {
    useWorkbenchStore.getState().setRightPanelTab("preview")
    useWorkbenchStore.getState().setSelectedBlockId("block_hello")
    render(<App />)

    fireEvent.click(screen.getByRole("button", { name: "当前块" }))

    const expected = getCurrentRenderResult(useWorkbenchStore.getState(), { targetBlockId: "block_hello" }).output
    expect(screen.getByTestId("preview-tab")).toHaveTextContent("block_hello")
    expect(screen.getByTestId("preview-output").textContent).toBe(expected)
  })

  it("shows missing variable details in preview", () => {
    useWorkbenchStore.getState().setCurrentDataset("dataset_empty")
    useWorkbenchStore.getState().setRightPanelTab("preview")
    render(<App />)

    expect(screen.getByTestId("preview-tab")).toHaveTextContent("user.name")
  })

  it("groups render errors, warnings, and logs in the debug tab", () => {
    useWorkbenchStore.getState().setRightPanelTab("debug")
    render(<App />)

    expect(screen.getByTestId("debug-tab")).toHaveTextContent("Errors")
    expect(screen.getByTestId("debug-tab")).toHaveTextContent("Warnings")
    expect(screen.getByTestId("debug-tab")).toHaveTextContent("condition user.isVip")
    expect(screen.getByTestId("debug-tab")).toHaveTextContent("loop items")
    expect(screen.getByTestId("debug-tab")).toHaveTextContent("fragment fragment_common_footer")
  })
})
