import { LitElement, html } from 'lit'
import {
  QueryClient,
  QueryClientProvider,
  createMutationController,
  createQueryController,
  useIsFetching,
  useIsMutating,
} from '@tanstack/lit-query'
import {
  addTodoOnServer,
  failNextFetchRequest,
  failNextMutationRequest,
  fetchTodosFromServer,
  resetTodoApi,
} from './todoApi'
import type { Todo, TodosResponse } from './todoApi'

resetTodoApi()

const demoQueryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: false,
    },
    mutations: {
      retry: false,
    },
  },
})

class DemoQueryProvider extends QueryClientProvider {
  constructor() {
    super()
    this.client = demoQueryClient
  }

  protected override createRenderRoot(): HTMLElement | DocumentFragment {
    return this
  }
}

customElements.define('demo-query-provider', DemoQueryProvider)

class TanstackLitQueryDemo extends LitElement {
  static properties = {
    nextTodoTitle: { state: true },
    cacheSeedCount: { state: true },
  }

  private nextTodoTitle = 'Add mutation assertion'
  private cacheSeedCount = 0

  private readonly todosQuery = createQueryController<TodosResponse, Error>(
    this,
    {
      queryKey: ['todos'],
      queryFn: fetchTodosFromServer,
    },
  )

  private readonly createTodoMutation = createMutationController<
    Todo,
    Error,
    string
  >(this, {
    mutationKey: ['create-todo'],
    mutationFn: addTodoOnServer,
    onSuccess: (createdTodo) => {
      demoQueryClient.setQueryData<TodosResponse>(['todos'], (existing) => {
        if (!existing) {
          return {
            items: [createdTodo],
            requestCount: 0,
            source: 'cache',
          }
        }

        return {
          items: [...existing.items, createdTodo],
          requestCount: existing.requestCount,
          source: 'cache',
        }
      })
    },
  })

  private readonly isFetching = useIsFetching(this, {
    queryKey: ['todos'],
  })

  private readonly isMutating = useIsMutating(this, {
    mutationKey: ['create-todo'],
  })

  protected override createRenderRoot(): HTMLElement | DocumentFragment {
    return this
  }

  private onTitleInput(event: Event): void {
    const target = event.target as HTMLInputElement
    this.nextTodoTitle = target.value
  }

  private addTodo(): void {
    const title = this.nextTodoTitle.trim()
    if (!title) {
      return
    }

    this.createTodoMutation.mutate(title)
    this.nextTodoTitle = ''
  }

  private async invalidateTodos(): Promise<void> {
    await demoQueryClient.invalidateQueries({ queryKey: ['todos'] })
  }

  private seedCacheOnlyTodo(): void {
    this.cacheSeedCount += 1

    const seedTodo: Todo = {
      id: 10_000 + this.cacheSeedCount,
      title: `Seeded cache todo ${this.cacheSeedCount}`,
    }

    demoQueryClient.setQueryData<TodosResponse>(['todos'], (existing) => {
      if (!existing) {
        return {
          items: [seedTodo],
          requestCount: 0,
          source: 'cache',
        }
      }

      return {
        items: [...existing.items, seedTodo],
        requestCount: existing.requestCount,
        source: 'cache',
      }
    })
  }

  private forceNextFetchFailure(): void {
    failNextFetchRequest()
  }

  private forceNextMutationFailure(): void {
    failNextMutationRequest()
  }

  private async resetDemoState(): Promise<void> {
    resetTodoApi()
    this.cacheSeedCount = 0
    this.nextTodoTitle = 'Add mutation assertion'

    await demoQueryClient.resetQueries({ queryKey: ['todos'] })
    this.createTodoMutation.reset()
  }

  render() {
    const query = this.todosQuery()
    const mutation = this.createTodoMutation()
    const todos = query.data?.items ?? []

    return html`
      <main>
        <h1>TanStack Lit Query E2E Demo</h1>
        <p>Verifies integration between Lit, query-core, and this adapter.</p>

        <section>
          <div data-testid="query-status">query: ${query.status}</div>
          <div data-testid="mutation-status">mutation: ${mutation.status}</div>
          <div data-testid="active-fetches">fetches: ${this.isFetching()}</div>
          <div data-testid="active-mutations">
            mutations: ${this.isMutating()}
          </div>
          <div data-testid="request-count">
            server-requests: ${query.data?.requestCount ?? 0}
          </div>
          <div data-testid="data-source">
            source: ${query.data?.source ?? 'none'}
          </div>
        </section>

        <section>
          <button
            data-testid="refetch"
            @click=${() => this.todosQuery.refetch()}
          >
            Refetch
          </button>
          <button
            data-testid="invalidate"
            @click=${() => this.invalidateTodos()}
          >
            Invalidate
          </button>
          <button
            data-testid="seed-cache"
            @click=${() => this.seedCacheOnlyTodo()}
          >
            Seed Cache
          </button>
          <button
            data-testid="reset-demo-state"
            @click=${() => this.resetDemoState()}
          >
            Reset Demo State
          </button>
          <button
            data-testid="fail-next-fetch"
            @click=${() => this.forceNextFetchFailure()}
          >
            Fail Next Fetch
          </button>
          <button
            data-testid="fail-next-mutation"
            @click=${() => this.forceNextMutationFailure()}
          >
            Fail Next Mutation
          </button>
        </section>

        <section>
          <label for="newTodoInput">New todo</label>
          <input
            id="newTodoInput"
            data-testid="new-todo-input"
            .value=${this.nextTodoTitle}
            @input=${this.onTitleInput}
          />
          <button data-testid="add-todo" @click=${() => this.addTodo()}>
            Add Todo (Mutation)
          </button>
        </section>

        ${query.isError
          ? html`<div data-testid="query-error">${String(query.error)}</div>`
          : null}
        ${mutation.isError
          ? html`<div data-testid="mutation-error">
              ${String(mutation.error)}
            </div>`
          : null}

        <ul data-testid="todo-list">
          ${todos.map(
            (todo) =>
              html`<li data-testid="todo-item">${todo.id}: ${todo.title}</li>`,
          )}
        </ul>
      </main>
    `
  }
}

customElements.define('tanstack-lit-query-demo', TanstackLitQueryDemo)

class DemoRoot extends LitElement {
  protected override createRenderRoot(): HTMLElement | DocumentFragment {
    return this
  }

  render() {
    return html`
      <demo-query-provider>
        <tanstack-lit-query-demo></tanstack-lit-query-demo>
      </demo-query-provider>
    `
  }
}

customElements.define('demo-root', DemoRoot)
import { LitElement, html } from 'lit'
import {
  QueryClient,
  QueryClientProvider,
  createMutationController,
  createQueryController,
  useIsFetching,
  useIsMutating,
} from '@tanstack/lit-query'
import {
  addTodoOnServer,
  failNextFetchRequest,
  failNextMutationRequest,
  fetchTodosFromServer,
  resetTodoApi,
} from './todoApi'
import type { Todo, TodosResponse } from './todoApi'

resetTodoApi()

const demoQueryClient = new QueryClient({
  defaultOptions: {
    queries: {
      retry: false,
    },
    mutations: {
      retry: false,
    },
  },
})

class DemoQueryProvider extends QueryClientProvider {
  constructor() {
    super()
    this.client = demoQueryClient
  }

  protected override createRenderRoot(): HTMLElement | DocumentFragment {
    return this
  }
}

customElements.define('demo-query-provider', DemoQueryProvider)

class TanstackLitQueryDemo extends LitElement {
  static properties = {
    nextTodoTitle: { state: true },
    cacheSeedCount: { state: true },
  }

  private nextTodoTitle = 'Add mutation assertion'
  private cacheSeedCount = 0

  private readonly todosQuery = createQueryController<TodosResponse, Error>(
    this,
    {
      queryKey: ['todos'],
      queryFn: fetchTodosFromServer,
    },
  )

  private readonly createTodoMutation = createMutationController<
    Todo,
    Error,
    string
  >(this, {
    mutationKey: ['create-todo'],
    mutationFn: addTodoOnServer,
    onSuccess: (createdTodo) => {
      demoQueryClient.setQueryData<TodosResponse>(['todos'], (existing) => {
        if (!existing) {
          return {
            items: [createdTodo],
            requestCount: 0,
            source: 'cache',
          }
        }

        return {
          items: [...existing.items, createdTodo],
          requestCount: existing.requestCount,
          source: 'cache',
        }
      })
    },
  })

  private readonly isFetching = useIsFetching(this, {
    queryKey: ['todos'],
  })

  private readonly isMutating = useIsMutating(this, {
    mutationKey: ['create-todo'],
  })

  protected override createRenderRoot(): HTMLElement | DocumentFragment {
    return this
  }

  private onTitleInput(event: Event): void {
    const target = event.target as HTMLInputElement
    this.nextTodoTitle = target.value
  }

  private addTodo(): void {
    const title = this.nextTodoTitle.trim()
    if (!title) {
      return
    }

    this.createTodoMutation.mutate(title)
    this.nextTodoTitle = ''
  }

  private async invalidateTodos(): Promise<void> {
    await demoQueryClient.invalidateQueries({ queryKey: ['todos'] })
  }

  private seedCacheOnlyTodo(): void {
    this.cacheSeedCount += 1

    const seedTodo: Todo = {
      id: 10_000 + this.cacheSeedCount,
      title: `Seeded cache todo ${this.cacheSeedCount}`,
    }

    demoQueryClient.setQueryData<TodosResponse>(['todos'], (existing) => {
      if (!existing) {
        return {
          items: [seedTodo],
          requestCount: 0,
          source: 'cache',
        }
      }

      return {
        items: [...existing.items, seedTodo],
        requestCount: existing.requestCount,
        source: 'cache',
      }
    })
  }

  private forceNextFetchFailure(): void {
    failNextFetchRequest()
  }

  private forceNextMutationFailure(): void {
    failNextMutationRequest()
  }

  private async resetDemoState(): Promise<void> {
    resetTodoApi()
    this.cacheSeedCount = 0
    this.nextTodoTitle = 'Add mutation assertion'

    await demoQueryClient.resetQueries({ queryKey: ['todos'] })
    this.createTodoMutation.reset()
  }

  render() {
    const query = this.todosQuery()
    const mutation = this.createTodoMutation()
    const todos = query.data?.items ?? []

    return html`
      <main>
        <h1>TanStack Lit Query E2E Demo</h1>
        <p>Verifies integration between Lit, query-core, and this adapter.</p>

        <section>
          <div data-testid="query-status">query: ${query.status}</div>
          <div data-testid="mutation-status">mutation: ${mutation.status}</div>
          <div data-testid="active-fetches">fetches: ${this.isFetching()}</div>
          <div data-testid="active-mutations">
            mutations: ${this.isMutating()}
          </div>
          <div data-testid="request-count">
            server-requests: ${query.data?.requestCount ?? 0}
          </div>
          <div data-testid="data-source">
            source: ${query.data?.source ?? 'none'}
          </div>
        </section>

        <section>
          <button
            data-testid="refetch"
            @click=${() => this.todosQuery.refetch()}
          >
            Refetch
          </button>
          <button
            data-testid="invalidate"
            @click=${() => this.invalidateTodos()}
          >
            Invalidate
          </button>
          <button
            data-testid="seed-cache"
            @click=${() => this.seedCacheOnlyTodo()}
          >
            Seed Cache
          </button>
          <button
            data-testid="reset-demo-state"
            @click=${() => this.resetDemoState()}
          >
            Reset Demo State
          </button>
          <button
            data-testid="fail-next-fetch"
            @click=${() => this.forceNextFetchFailure()}
          >
            Fail Next Fetch
          </button>
          <button
            data-testid="fail-next-mutation"
            @click=${() => this.forceNextMutationFailure()}
          >
            Fail Next Mutation
          </button>
        </section>

        <section>
          <label for="newTodoInput">New todo</label>
          <input
            id="newTodoInput"
            data-testid="new-todo-input"
            .value=${this.nextTodoTitle}
            @input=${this.onTitleInput}
          />
          <button data-testid="add-todo" @click=${() => this.addTodo()}>
            Add Todo (Mutation)
          </button>
        </section>

        ${query.isError
          ? html`<div data-testid="query-error">${String(query.error)}</div>`
          : null}
        ${mutation.isError
          ? html`<div data-testid="mutation-error">
              ${String(mutation.error)}
            </div>`
          : null}

        <ul data-testid="todo-list">
          ${todos.map(
            (todo) =>
              html`<li data-testid="todo-item">${todo.id}: ${todo.title}</li>`,
          )}
        </ul>
      </main>
    `
  }
}

customElements.define('tanstack-lit-query-demo', TanstackLitQueryDemo)

class DemoRoot extends LitElement {
  protected override createRenderRoot(): HTMLElement | DocumentFragment {
    return this
  }

  render() {
    return html`
      <demo-query-provider>
        <tanstack-lit-query-demo></tanstack-lit-query-demo>
      </demo-query-provider>
    `
  }
}

customElements.define('demo-root', DemoRoot)