"use client"

import {
  startTransition,
  useCallback,
  useEffect,
  useMemo,
  useOptimistic,
  useRef,
  useState,
  useTransition,
} from "react"

type Project = {
  id: string
  name: string
  ownerId: string
  revision: number
}

type Activity = {
  id: string
  projectId: string
  message: string
  createdAt: string
}

type Draft = {
  name: string
  ownerId: string
}

type LoadState =
  | { status: "idle" }
  | { status: "loading"; projectId: string }
  | { status: "ready"; projectId: string }
  | { status: "error"; projectId: string; message: string }

async function readJson<T>(response: Response): Promise<T> {
  if (!response.ok) {
    throw new Error(`Request failed with HTTP ${response.status}.`)
  }
  return await response.json() as T
}

async function getProject(projectId: string, signal?: AbortSignal): Promise<Project> {
  const response = await fetch(`/api/projects/${encodeURIComponent(projectId)}`, {
    signal,
    headers: { accept: "application/json" },
  })
  return await readJson<Project>(response)
}

async function getActivity(projectId: string, signal?: AbortSignal): Promise<Activity[]> {
  const response = await fetch(
    `/api/projects/${encodeURIComponent(projectId)}/activity`,
    { signal, headers: { accept: "application/json" } },
  )
  return await readJson<Activity[]>(response)
}

async function saveProject(project: Project, draft: Draft): Promise<Project> {
  const response = await fetch(`/api/projects/${encodeURIComponent(project.id)}`, {
    method: "PUT",
    headers: {
      "content-type": "application/json",
      "if-match": String(project.revision),
    },
    body: JSON.stringify(draft),
  })
  return await readJson<Project>(response)
}

async function postActivity(projectId: string, message: string): Promise<Activity> {
  const response = await fetch(
    `/api/projects/${encodeURIComponent(projectId)}/activity`,
    {
      method: "POST",
      headers: { "content-type": "application/json" },
      body: JSON.stringify({ message }),
    },
  )
  return await readJson<Activity>(response)
}

type ActivityAction =
  | { type: "append"; activity: Activity }
  | { type: "replace"; activity: Activity }
  | { type: "remove"; id: string }

function activityReducer(current: Activity[], action: ActivityAction): Activity[] {
  switch (action.type) {
    case "append":
      return [action.activity, ...current]
    case "replace":
      return current.map((item) => item.id === action.activity.id ? action.activity : item)
    case "remove":
      return current.filter((item) => item.id !== action.id)
  }
}

export function ProjectWorkspace({ initialProjectId }: { initialProjectId: string }) {
  const [projectId, setProjectId] = useState(initialProjectId)
  const [project, setProject] = useState<Project | null>(null)
  const [draft, setDraft] = useState<Draft>({ name: "", ownerId: "" })
  const [activity, setActivity] = useState<Activity[]>([])
  const [loadState, setLoadState] = useState<LoadState>({ status: "idle" })
  const [saveError, setSaveError] = useState<string | null>(null)
  const [isNavigating, startNavigation] = useTransition()
  const [isSaving, startSaving] = useTransition()
  const [optimisticActivity, updateOptimisticActivity] = useOptimistic(
    activity,
    activityReducer,
  )
  const selectedProjectRef = useRef(projectId)

  useEffect(() => {
    selectedProjectRef.current = projectId
  }, [projectId])

  useEffect(() => {
    if (!projectId) {
      setProject(null)
      setActivity([])
      setLoadState({ status: "idle" })
      return
    }

    const controller = new AbortController()
    setLoadState({ status: "loading", projectId })
    setSaveError(null)

    void getProject(projectId, controller.signal)
      .then((nextProject) => {
        setProject(nextProject)
        setDraft({ name: nextProject.name, ownerId: nextProject.ownerId })
        return getActivity(nextProject.id, controller.signal)
      })
      .then((nextActivity) => {
        setActivity(nextActivity)
        setLoadState({ status: "ready", projectId })
      })
      .catch((error: unknown) => {
        if (error instanceof DOMException && error.name === "AbortError") return
        setLoadState({
          status: "error",
          projectId,
          message: error instanceof Error ? error.message : "Project loading failed.",
        })
      })

    return () => controller.abort("project changed")
  }, [projectId])

  const dirty = useMemo(() => {
    if (!project) return false
    return draft.name !== project.name || draft.ownerId !== project.ownerId
  }, [draft.name, draft.ownerId, project])

  const chooseProject = useCallback((nextProjectId: string) => {
    startNavigation(() => {
      setProjectId(nextProjectId)
    })
  }, [])

  const save = useCallback(() => {
    if (!project || !dirty || isSaving) return
    const previous = project
    const optimistic: Project = {
      ...project,
      ...draft,
      revision: project.revision + 1,
    }
    setProject(optimistic)
    setSaveError(null)

    startSaving(async () => {
      try {
        const saved = await saveProject(previous, draft)
        if (selectedProjectRef.current !== saved.id) return
        setProject(saved)
        setDraft({ name: saved.name, ownerId: saved.ownerId })
      } catch (error) {
        setProject(previous)
        setDraft({ name: previous.name, ownerId: previous.ownerId })
        setSaveError(error instanceof Error ? error.message : "Save failed.")
      }
    })
  }, [dirty, draft, isSaving, project])

  const addActivity = useCallback((message: string) => {
    if (!project || !message.trim()) return
    const temporary: Activity = {
      id: `optimistic-${crypto.randomUUID()}`,
      projectId: project.id,
      message: message.trim(),
      createdAt: new Date().toISOString(),
    }
    startTransition(async () => {
      updateOptimisticActivity({ type: "append", activity: temporary })
      try {
        const saved = await postActivity(project.id, temporary.message)
        setActivity((current) => [saved, ...current])
      } catch {
        updateOptimisticActivity({ type: "remove", id: temporary.id })
      }
    })
  }, [project, updateOptimisticActivity])

  const reloadActivity = useCallback(async () => {
    if (!project) return
    const nextActivity = await getActivity(project.id)
    setActivity(nextActivity)
  }, [project])

  return (
    <main className="workspace">
      <aside aria-label="Projects">
        {[["alpha", "Payments"], ["beta", "Search"], ["gamma", "Accounts"]].map(([id, label]) => (
          <button
            key={id}
            type="button"
            aria-pressed={projectId === id}
            disabled={isNavigating}
            onClick={() => chooseProject(id)}
          >
            {label}
          </button>
        ))}
      </aside>

      <section aria-busy={loadState.status === "loading" || isNavigating}>
        {loadState.status === "loading" ? <p>Loading {loadState.projectId}…</p> : null}
        {loadState.status === "error" ? (
          <div role="alert">
            <p>{loadState.message}</p>
            <button type="button" onClick={() => setProjectId(loadState.projectId)}>
              Try again
            </button>
          </div>
        ) : null}

        {project ? (
          <form onSubmit={(event) => { event.preventDefault(); save() }}>
            <label>
              Project name
              <input
                value={draft.name}
                onChange={(event) => setDraft((current) => ({
                  ...current,
                  name: event.target.value,
                }))}
              />
            </label>
            <button type="submit" disabled={!dirty || isSaving}>
              {isSaving ? "Saving…" : "Save project"}
            </button>
            {saveError ? <p role="alert">{saveError}</p> : null}
          </form>
        ) : null}

        <ActivityComposer onAdd={addActivity} />
        <button type="button" onClick={() => void reloadActivity()}>
          Refresh activity
        </button>
        <ol>
          {optimisticActivity.map((item) => (
            <li key={item.id} data-pending={item.id.startsWith("optimistic-") || undefined}>
              <p>{item.message}</p>
              <time dateTime={item.createdAt}>{new Date(item.createdAt).toLocaleString()}</time>
            </li>
          ))}
        </ol>
      </section>
    </main>
  )
}

function ActivityComposer({ onAdd }: { onAdd(message: string): void }) {
  const [message, setMessage] = useState("")
  return (
    <form onSubmit={(event) => {
      event.preventDefault()
      onAdd(message)
      setMessage("")
    }}>
      <label>
        Activity message
        <textarea value={message} onChange={(event) => setMessage(event.target.value)} />
      </label>
      <button type="submit" disabled={!message.trim()}>Add activity</button>
    </form>
  )
}
